Introduction
This Rmarkdown file summarises all code used to process, analyse, and visualise the data. However, note that the code for all statistical models requires extensive computational power, and ran on the computational cluster Katana supported by Research Technology Services at the University of New South Wales, Sydney (https://research.unsw.edu.au/katana). Please see the folder /R to find the files that were used to run the statistical models separately and the /pbs folder to find the resources needed to run each model. If you have any questions or find mistakes in the code presented below, please contact Patrice Pottier at p.pottier@unsw.edu.au
Load packages
pacman::p_load(tidyverse, purrr, ape, rotl, brms, orchaRd, metafor, emmeans, tidybayes,
ggbeeswarm, kableExtra, ggtree, ggtreeExtra)
set.seed(234)
Data wrangling
Load raw data
# Load raw extracted data
raw_data <- read_csv("data_extraction/all_extracted_data.csv")
raw_data <- as.data.frame(raw_data)
# Rename body size values
raw_data$trait_type[raw_data$trait_type == "body size"] <- "body_size"
raw_data <- rename(raw_data, peer_reviewed = "peer-reviewed")
# Calculate the temperature difference between selection temperatures (in
# absolute values because it is modeled independently for warmer and colder
# lines)
raw_data <- raw_data %>%
mutate(select_temp_diff = abs(treatment_temp - control_temp))
# Calculate the temperature difference between assay and control temperature
raw_data <- raw_data %>%
mutate(assay_temp_diff = assay_temp - control_temp)
Convert confidence intervals to SE, and SE to SD
To use effective sample size properly, we need to first back-convert estimates reported as SE to SD.
processed_data <- raw_data %>%
mutate(error_control = ifelse(error_type == "ci", error_control/1.96, error_control),
error_treatment = ifelse(error_type == "ci", error_treatment/1.96, error_treatment)) %>%
mutate(error_type = ifelse(error_type == "ci", "se", error_type))
processed_data <- raw_data %>%
mutate(sd_control = ifelse(error_type == "se", error_control * sqrt(n_used_control),
error_control), sd_treatment = ifelse(error_type == "se", error_treatment *
sqrt(n_used_treatment), error_treatment))
processed_data <- processed_data %>%
mutate(sd_control = ifelse(sd_control == 0, NA, sd_control), sd_treatment = ifelse(sd_treatment ==
0, NA, sd_treatment)) # Replace values of 0 for the error by NA, as these result from a lack of sampling.
Calculate effective sample sizes
Because the true level of replication in experimental evolution studies is the number of replicate selection lines, we calculated effective sample sizes to account for pseudo-replication. Effective sample sizes were calculated from formulas in Rutkowska et al. 2013. https://doi.org/10.1111/jeb.12282 ICC was taken as 0.5, which is conservative. It assumes that life history traits are repeatable between selection lines, so the effective sample size is closer to the number of lines than it is to the total number of individuals tested. When the number of animals was not available, the sample size was taken as the number of replicates.
# Calculate effective sample sizes
processed_data <- processed_data %>%
mutate(eff_n_control= ifelse(is.na(n_animals_control)==TRUE,
n_control,
n_animals_control / (1 + 0.5*((n_animals_control/n_control)-1))),
eff_n_treatment= ifelse(is.na(n_animals_treatment)==TRUE,
n_treatment,
n_animals_treatment / (1 + 0.5*((n_animals_treatment/n_treatment)-1))))
Calculate effect sizes
Custom functions
Here, we use custom functions to calculate the average coefficient of variation and impute missing standard deviations. See Nakagawa et al. 2023 (Methods in Ecology and Evolution)
cv_avg <- function(x, sd, n, group, data, label = NULL, sub_b = TRUE, cv2 = FALSE) {
# Check if the name is specified or not. If not, then assign it the name of
# the mean, x, variable input in the function.
# https://stackoverflow.com/questions/60644445/converting-tidyeval-arguments-to-string
if (is.null(label)) {
label <- purrr::map_chr(enquos(x), rlang::as_label)
}
# Calculate between study CV. Take weighted mean CV within study, and then
# take a weighted mean across studies of the within study CV. Weighted
# based on sample size and pooled sample size.
b_grp_cv_data <- data %>%
dplyr::group_by({
{
group
}
}) %>%
dplyr::mutate(w_CV2 = weighted_CV({
{
sd
}
}, {
{
x
}
}, {
{
n
}
}, cv2 = cv2), n_mean = mean({
{
n
}
}, na.rm = TRUE)) %>%
dplyr::ungroup(.) %>%
dplyr::mutate(b_CV2 = weighted.mean(w_CV2, n_mean, na.rm = TRUE), .keep = "used")
# Make sure that label of the calculated columns is distinct from any other
# columns
names(b_grp_cv_data) <- paste0(names(b_grp_cv_data), "_", label)
# Append these calculated columns back to the original data and return the
# full dataset.
if (sub_b) {
b_grp_cv_data <- b_grp_cv_data %>%
dplyr::select(grep("b_", names(b_grp_cv_data)))
dat_new <- cbind(data, b_grp_cv_data)
} else {
dat_new <- cbind(data, b_grp_cv_data)
}
return(data.frame(dat_new))
}
# Helper function
weighted_CV <- function(sd, x, n, cv2 = FALSE) {
if (cv2) {
weighted.mean(na_if((sd/x)^2, Inf), n, na.rm = TRUE)
} else {
weighted.mean(na_if((sd/x), Inf), n, na.rm = TRUE)^2
}
}
Impute missing standard deviations and calculate effect sizes
Different formulas were used for independent and dependent samples (see methods).
# Calculate average standard deviations
processed_data <- cv_avg(x=mean_control, sd = sd_control, n=eff_n_control, group = ref, label = "control", data = processed_data) # Control
processed_data <- cv_avg(x=mean_treatment, sd = sd_treatment, n=eff_n_treatment, group = ref, label = "treatment", data = processed_data) # Treatment
# Multiply the coefficient of variation by the mean to get SD
processed_data <- processed_data %>% mutate(sd_control = ifelse(is.na(sd_control)==TRUE,
b_CV2_control*mean_control,
sd_control),
sd_treatment = ifelse(is.na(sd_treatment)==TRUE,
b_CV2_treatment*mean_treatment,
sd_treatment))
# Add a column to identify observations sharing the same cohort IDs
processed_data <- processed_data %>%
group_by(cohort_ID) %>%
mutate(n_cohort = n()) %>%
ungroup()
# Calculate effect sizes for independent and dependent (traits measured on the same animals) samples.
processed_data <- processed_data %>% mutate(
# Log response ratio (lnRR)
lnRR = log(mean_treatment/mean_control) +
(0.5 * ((sd_treatment^2/(eff_n_treatment*mean_treatment^2)) -
(sd_control^2/(eff_n_control*mean_control^2)))), # Formula 4 in Senior et al. 2020
# Sampling variance of lnRR
var_lnRR = ifelse(n_cohort == 1, # If samples are independent
(sd_control^2/(eff_n_control*mean_control^2)) +
(sd_control^4/(2*eff_n_control^2*mean_control^4)) +
(sd_treatment^2/(eff_n_treatment*mean_treatment^2)) +
(sd_treatment^4/(2*eff_n_treatment^2*mean_treatment^4)), # Formula 14 in Senior et al. 2020
(sd_control^2/(eff_n_control*mean_control^2)) + # else use this formula if samples are dependent
(sd_treatment^2/(eff_n_treatment*mean_treatment^2)) +
(sd_control^4/(2*eff_n_control^2*mean_control^4)) +
(sd_treatment^4/(2*eff_n_treatment^2*mean_treatment^4)) -
(0.5 * ((2*sd_control*sd_treatment)/(eff_n_control*mean_control*mean_treatment))) +
(0.5^2 * ((sd_control^2*sd_treatment^2*(mean_control^4+mean_treatment^4))/(2*eff_n_control^2*mean_control^4*mean_treatment^4))) # Formula 19 in Senior et al. 2020
),
# Log variance ratio (lnVR)
lnVR = log(sd_treatment/sd_control) +
(0.5 * ((1/(eff_n_treatment-1)) -
(1/(eff_n_control-1)))), # Formula 5 in Senior et al. 2020
# Sampling variance of lnVR
var_lnVR = ifelse(n_cohort ==1, # If samples are independent
0.5 * ((eff_n_control/((eff_n_control-1)^2)) + (eff_n_treatment/((eff_n_treatment-1)^2))), # Formula 15 in Senior et al. 2020
(eff_n_control/(eff_n_control - 1)^2) - (0.5^2*(1/(eff_n_control-1))) + (0.5^4*((sd_control^8+sd_treatment^8)/(2*((eff_n_control-1)^2)*sd_control^4*sd_treatment^4))) # Formula 22 in Senior et al. 2020
),
# Log coefficient of variation ratio (lnCVR)
lnCVR = log((sd_treatment/mean_treatment)/(sd_control/mean_control)) +
(0.5 * ((1/(eff_n_treatment-1)) -
(1/(eff_n_control-1)))) +
(0.5 * ((sd_control^2/(eff_n_control*mean_control^2)) -
(sd_treatment^2/(eff_n_treatment*mean_treatment^2)))), # Formula 6 in Senior et al. 2020
# Sampling variance of lnCVR
var_lnCVR = ifelse(n_cohort==1, # If samples are independent
(sd_control^2/(eff_n_control*mean_control^2)) +
(sd_control^4/(2*eff_n_control^2*mean_control^4)) +
(eff_n_control/((eff_n_control-1)^2)) +
(sd_treatment^2/(eff_n_treatment*mean_treatment^2)) +
(sd_treatment^4/(2*eff_n_treatment^2*mean_treatment^4)) +
(eff_n_treatment/((eff_n_treatment-1)^2)), # Formula 16 in Senior et al. 2020
(sd_control^2/(eff_n_control*mean_control^2)) +
(sd_treatment^2/(eff_n_treatment*mean_treatment^2)) +
(sd_control^4/(2*eff_n_control^2*mean_control^4)) +
(sd_treatment^4/(2*eff_n_treatment^2*mean_treatment^4)) -
(0.5 * ((2*sd_control*sd_treatment)/(eff_n_control*mean_control*mean_treatment))) +
(0.5^2 * (sd_control^2*sd_treatment^2*(mean_control^4+mean_treatment^4))/(2*eff_n_control^2*mean_control^4*mean_treatment^4)) +
(eff_n_control/((eff_n_control-1)^2)) -
(0.5^2 * (1/(eff_n_control-1))) +
(0.5^4 * (sd_control^8+sd_treatment^8)/(2*((eff_n_control-1)^2)*sd_control^4*sd_treatment^4)) # Formula 24 in Senior et al. 2020
))
Save processed dataset
# Save processed data
write.csv(processed_data, file = "data/processed_data.csv")
Account for data non-independence
Phylogenetic variation
processed_data <- read_csv(file = "data/processed_data.csv")
data <- processed_data
unique(data$species) # 25 species
## [1] "Drosophila melanogaster" "Sepsis punctum"
## [3] "Musca domestica" "Poecilia reticulata"
## [5] "Daphnia pulicaria" "Ophryotrocha labronica"
## [7] "Callosobruchus maculatus" "Tribolium castaneum"
## [9] "Caenorhabditis remanei" "Drosophila serrata"
## [11] "Apocyclops royi" "Rhizoglyphus robini"
## [13] "Drosophila subobscura" "Zabrotes subfasciatus"
## [15] "Eurytemora affinis" "Drosophila buzzatii"
## [17] "Callosobruchus chinensis" "Drosophila bipectinata"
## [19] "Drosophila hydei" "Drosophila pseudoananassae"
## [21] "Simocephalus vetulus" "Daphnia magna"
## [23] "Daphnia pulex" "Brachionus plicatilis"
## [25] "Danio rerio"
# Match species to the OTL database
taxa <- unique(data$species)
resolved_names <- tnrs_match_names(taxa, context_name = "Animals")
resolved_names # All good, no approximate matches
## search_string unique_name approximate_match
## 1 drosophila melanogaster Drosophila melanogaster FALSE
## 2 sepsis punctum Sepsis punctum FALSE
## 3 musca domestica Musca domestica FALSE
## 4 poecilia reticulata Poecilia reticulata FALSE
## 5 daphnia pulicaria Daphnia pulicaria FALSE
## 6 ophryotrocha labronica Ophryotrocha labronica FALSE
## 7 callosobruchus maculatus Callosobruchus maculatus FALSE
## 8 tribolium castaneum Tribolium castaneum FALSE
## 9 caenorhabditis remanei Caenorhabditis remanei FALSE
## 10 drosophila serrata Drosophila serrata FALSE
## 11 apocyclops royi Apocyclops royi FALSE
## 12 rhizoglyphus robini Rhizoglyphus robini FALSE
## 13 drosophila subobscura Drosophila subobscura FALSE
## 14 zabrotes subfasciatus Zabrotes subfasciatus FALSE
## 15 eurytemora affinis Eurytemora affinis FALSE
## 16 drosophila buzzatii Drosophila buzzatii FALSE
## 17 callosobruchus chinensis Callosobruchus chinensis FALSE
## 18 drosophila bipectinata Drosophila bipectinata FALSE
## 19 drosophila hydei Drosophila hydei FALSE
## 20 drosophila pseudoananassae Drosophila pseudoananassae FALSE
## 21 simocephalus vetulus Simocephalus vetulus FALSE
## 22 daphnia magna Daphnia magna FALSE
## 23 daphnia pulex Daphnia pulex FALSE
## 24 brachionus plicatilis Brachionus plicatilis FALSE
## 25 danio rerio Danio rerio FALSE
## ott_id is_synonym flags number_matches
## 1 505714 FALSE 1
## 2 589555 FALSE sibling_higher 1
## 3 1011010 FALSE 1
## 4 312452 FALSE 1
## 5 668394 FALSE sibling_higher 1
## 6 864359 FALSE 1
## 7 460613 FALSE 1
## 8 148904 FALSE 1
## 9 396902 FALSE 1
## 10 86632 FALSE 1
## 11 396577 FALSE 1
## 12 7742 FALSE 1
## 13 660827 FALSE 2
## 14 110954 FALSE 1
## 15 850982 FALSE 1
## 16 607957 FALSE 1
## 17 986312 FALSE 1
## 18 733212 FALSE 1
## 19 505712 FALSE 1
## 20 506208 FALSE 1
## 21 424900 FALSE 1
## 22 668392 FALSE sibling_higher 1
## 23 59086 FALSE sibling_higher 1
## 24 471703 FALSE 1
## 25 1005914 FALSE 1
# Create tree
tree <- tol_induced_subtree(ott_ids = resolved_names$ott_id)
tree$tip.label <- strip_ott_ids(tree$tip.label) # Remove ott IDs for presentation
# plot(tree, no.margin=TRUE, cex=1)
tree <- multi2di(tree, random = TRUE) # Resolve polytomy at random, but it matches classification from Cornetti et al. 2019. Molecular Phylogenetics and Evolution; with D. pulex and D. pulicaria being more closely related to eachother than D. magna
phylo_tree <- compute.brlen(tree, method = "Grafen", power = 1) # Compute branch lengths
# Plot tree
plot(phylo_tree)
# Compute phylogenetic correlation matrix
phylo_matrix <- vcv(phylo_tree, cor = T) # The vcv function returns a variance-covariance matrix
# Add an additional column for phylogeny
data <- mutate(data, phylogeny = gsub(" ", "_", species), obs = row_number())
# Convert tibble to data frame
data <- as.data.frame(data)
# Rename trait type
data$trait_type <- factor(data$trait_type, levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
# Rename temperature regime
data$warm_cold <- factor(data$warm_cold, levels = c("cold", "warm"), labels = c("Cold",
"Warm"))
Overview of the dataset
# Summarize overall metrics
overall <- data %>%
summarise(Group = "Overall", `Number of Studies` = n_distinct(ref), `Number of Species` = n_distinct(species),
`Number of Effect Sizes` = n_distinct(obs))
# Summarize by trait
by_trait <- data %>%
group_by(trait_type) %>%
summarise(Group = paste("Trait -", unique(trait_type)), `Number of Studies` = n_distinct(ref),
`Number of Species` = n_distinct(species), `Number of Effect Sizes` = n_distinct(obs)) %>%
ungroup()
# Summarize by temperature regime
by_temperature <- data %>%
group_by(warm_cold) %>%
summarise(Group = paste("Temperature regime -", unique(warm_cold)), `Number of Studies` = n_distinct(ref),
`Number of Species` = n_distinct(species), `Number of Effect Sizes` = n_distinct(obs)) %>%
ungroup()
# Combine all summaries
table_sample_sizes <- bind_rows(overall, by_trait, by_temperature) %>%
dplyr::select(-trait_type, -warm_cold)
# Create the table
table_sample_sizes %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Group | Number of Studies | Number of Species | Number of Effect Sizes |
|---|---|---|---|
| Overall | 44 | 25 | 476 |
| Trait - Body size | 27 | 14 | 171 |
| Trait - Fecundity | 31 | 22 | 196 |
| Trait - Survival | 22 | 13 | 109 |
| Temperature regime - Cold | 13 | 5 | 95 |
| Temperature regime - Warm | 38 | 23 | 381 |
Main meta-analytic models
Note that these models take a substantial amount of time to run. Therefore, these models were ran on the computational cluster Katana, supported by Research Technology Services at UNSW Sydney. Resources needed to run these models are provided in the /pbs/ folder, and the R code used for each model is in the R/models/ folder.
Changes in trait means (lnRR)
Overall model
Model specification
# Model specification
formula <- bf(lnRR ~ trait_type -1 + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnRR)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnRR_model_overall <- brm(formula,
family = gaussian(),
data = data,
data2 = list(phylo_matrix = phylo_matrix,
VCV_lnRR = VCV_lnRR),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnRR_model_overall, file = "RData/lnRR_model_overall.rds")
Model output
# Load model
lnRR_model_overall <- readRDS("RData/lnRR_model_overall.rds")
# Display model output
summary(lnRR_model_overall)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnRR ~ trait_type - 1 + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnRR)
## Data: data (Number of observations: 476)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 88)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.05 0.01 0.03 0.06 1.00 2428 4079
##
## ~obs (Number of levels: 476)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.08 0.01 0.07 0.09 1.00 2314 4469
##
## ~phylogeny (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.02 0.02 0.00 0.06 1.00 4112 5342
##
## ~ref (Number of levels: 44)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.01 0.01 0.00
## sd(trait_typefecundity) 0.19 0.04 0.13
## sd(trait_typesurvival) 0.11 0.03 0.06
## cor(trait_typebody_size,trait_typefecundity) -0.14 0.47 -0.90
## cor(trait_typebody_size,trait_typesurvival) 0.03 0.49 -0.86
## cor(trait_typefecundity,trait_typesurvival) 0.08 0.50 -0.84
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.03 1.00 2591 4316
## sd(trait_typefecundity) 0.27 1.00 3834 5633
## sd(trait_typesurvival) 0.17 1.00 4383 5237
## cor(trait_typebody_size,trait_typefecundity) 0.80 1.01 627 1380
## cor(trait_typebody_size,trait_typesurvival) 0.89 1.00 1331 3070
## cor(trait_typefecundity,trait_typesurvival) 0.89 1.00 1888 3903
##
## ~species (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.01 0.01 0.00 0.04 1.00 5301 5459
##
## Regression Coefficients:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## trait_typebody_size -0.00 0.02 -0.04 0.04 1.00 5199 4640
## trait_typefecundity -0.02 0.04 -0.11 0.06 1.00 5736 6136
## trait_typesurvival 0.02 0.03 -0.04 0.09 1.00 6349 6309
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnRR_model_overall, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(Parameter = gsub("trait_type([a-z_]+)", "\\1", Parameter)) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size | -0.001 | -0.044 | 0.040 |
| fecundity | -0.022 | -0.106 | 0.061 |
| survival | 0.023 | -0.041 | 0.090 |
Heterogeneity analysis
# Extracting the posterior distribution from the model
posterior <- posterior_samples(lnRR_model_overall)
# Calculate measurement error variance
sigma2_v <- sum(1/diag(VCV_lnRR)) * (length(diag(VCV_lnRR))- 1)/(sum(1/diag(VCV_lnRR))^2 - sum((1/diag(VCV_lnRR))^2))
# Calculate the total variance (including the measurement error variance)
var_total <- posterior$sd_experiment_ID__Intercept +
posterior$sd_obs__Intercept +
posterior$sd_phylogeny__Intercept +
posterior$sd_ref__trait_typebody_size +
posterior$sd_ref__trait_typefecundity +
posterior$sd_ref__trait_typesurvival +
posterior$sd_species__Intercept +
sigma2_v
# Calculate heterogeneity (I2)
I2 <- list(
I2_total = (var_total - sigma2_v) / var_total, # Total heterogeneity
I2_study = (posterior$sd_ref__trait_typebody_size + # Heterogeneity explained by study differences
posterior$sd_ref__trait_typefecundity +
posterior$sd_ref__trait_typesurvival) / var_total,
I2_exp = posterior$sd_experiment_ID__Intercept / var_total, # Heterogeneity explained by experiment differences
I2_sp = posterior$sd_species__Intercept / var_total, # Heterogeneity explained by species differences
I2_phylo = posterior$sd_phylogeny__Intercept / var_total, # Heterogeneity explained by phylogenetic relatedness
I2_obs = posterior$sd_obs__Intercept / var_total # Heterogeneity explained by residual variation
)
# Calculate mean and credible intervals
summary_table <- t(sapply(I2, function(x) {
c(Mean = 100*round(mean(x),4),
` ` = 100* round(quantile(x, 0.025),4),
` ` = 100*round(quantile(x, 0.975),4))
}))
# Customise table
summary_table <- summary_table %>%
as.data.frame() %>%
rownames_to_column("Variable") %>%
mutate(
Variable = recode(Variable,
"I2_total" = "Total",
"I2_study" = "Study",
"I2_exp" = "Experiment",
"I2_sp" = "Species",
"I2_phylo" = "Phylogeny",
"I2_obs" = "Residual")
) %>%
rename(
lower_HPD = ` .2.5%`,
upper_HPD = ` .97.5%`
) %>%
mutate(
Variable = paste0("I<sup>2</sup><sub>", Variable) # Create a new column with formatted I² and subscripts
)
# Display table
kable(summary_table, "html", escape = FALSE,
col.names = c("Heterogeneity (%)", "Mean", "Lower HPD", "Upper HPD"),
align = c('l', 'r', 'r', 'r')) %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, width = "200px", bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Heterogeneity (%) | Mean | Lower HPD | Upper HPD |
|---|---|---|---|
| I2Total | 99.97 | 99.97 | 99.98 |
| I2Study | 65.40 | 55.25 | 73.94 |
| I2Experiment | 9.96 | 6.52 | 14.36 |
| I2Species | 2.77 | 0.11 | 7.91 |
| I2Phylogeny | 4.42 | 0.19 | 12.53 |
| I2Residual | 17.43 | 13.34 | 22.34 |
Data visualisation
# Generate predictions
emmeans_overall <- as.data.frame(emmeans(lnRR_model_overall, ~ trait_type))
emmeans_overall$trait_type <- factor(emmeans_overall$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
# Calculate sample sizes and study counts
sample_sizes_traits <- data %>%
group_by(trait_type) %>%
summarise(
estimates = n(),
studies = n_distinct(ref)
)
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75,
lwd=1) +
geom_quasirandom(data = data, # Plot effect sizes, scaled by precision
aes(x = trait_type,
y = lnRR,
size = 1/sqrt(var_lnRR)),
fill = "gray75",
shape = 21,
width = 0.25,
alpha = 0.2) +
geom_errorbar(data = emmeans_overall, # Plot the point estimates in white for background
aes(ymin = lower.HPD,
ymax = upper.HPD,
x = trait_type),
size = 2,
width = 0.085,
color = "white") +
geom_point(data = emmeans_overall, # Plot the credible intervals in white for background
aes(x = trait_type,
y = emmean),
size = 4.5,
stroke = 1.5,
shape = 21,
color="white",
fill = "white") +
geom_errorbar(data = emmeans_overall, # Plot the point estimates
aes(x = trait_type,
ymin = lower.HPD,
ymax = upper.HPD),
size = 1.5,
width = 0.075,
color = "black") +
geom_point(data = emmeans_overall, # Plot the credible intervals
aes(x = trait_type,
y = emmean),
size = 4,
shape = 21,
stroke = 1.5,
color="black",
fill = "white") +
geom_text(data = sample_sizes_traits, # Sample size annotations
aes(x = trait_type,
y = 1.5,
label = paste0("k = ", estimates, " (", studies, ")")),
hjust = 1, size = 5, show.legend = FALSE) +
theme_bw() + # Customize the plot
labs(y = "lnRR", x = "") +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 20, color = "black"),
legend.title = ggplot2::element_text(size = 16),
legend.text = ggplot2::element_text(size = 14),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5)) +
guides(color = "none", size = "none")+
coord_flip() +
ylim(-1.5, 1.5)
# Save figure
ggsave(file = "fig/lnRR_overall.png", width = 12, height = 7, dpi = 500)
Temperature regime
Model specification
# Model specification
formula <- bf(lnRR ~ trait_type:warm_cold -1 + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnRR)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnRR_model_warm_cold <- brm(formula,
family = gaussian(),
data = data,
data2 = list(phylo_matrix = phylo_matrix,
VCV_lnRR = VCV_lnRR),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnRR_model_warm_cold, file = "RData/lnRR_model_warm_cold.rds")
Model output
# Load model
lnRR_model_warm_cold <- readRDS("RData/lnRR_model_warm_cold.rds")
# Display model output
summary(lnRR_model_warm_cold)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnRR ~ trait_type:warm_cold - 1 + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnRR)
## Data: data (Number of observations: 476)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 88)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.05 0.01 0.03 0.06 1.00 2199 3535
##
## ~obs (Number of levels: 476)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.08 0.01 0.07 0.09 1.00 1978 3594
##
## ~phylogeny (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.02 0.02 0.00 0.07 1.00 2828 3846
##
## ~ref (Number of levels: 44)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.01 0.01 0.00
## sd(trait_typefecundity) 0.21 0.04 0.14
## sd(trait_typesurvival) 0.11 0.03 0.06
## cor(trait_typebody_size,trait_typefecundity) -0.14 0.48 -0.91
## cor(trait_typebody_size,trait_typesurvival) 0.03 0.49 -0.86
## cor(trait_typefecundity,trait_typesurvival) 0.00 0.49 -0.85
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.03 1.00 1823 3152
## sd(trait_typefecundity) 0.30 1.00 3014 4756
## sd(trait_typesurvival) 0.18 1.00 2917 3897
## cor(trait_typebody_size,trait_typefecundity) 0.81 1.01 439 934
## cor(trait_typebody_size,trait_typesurvival) 0.88 1.01 922 2295
## cor(trait_typefecundity,trait_typesurvival) 0.85 1.00 1462 2565
##
## ~species (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.01 0.01 0.00 0.04 1.00 3404 3916
##
## Regression Coefficients:
## Estimate Est.Error l-95% CI u-95% CI Rhat
## trait_typebody_size:warm_coldcold 0.01 0.02 -0.04 0.07 1.00
## trait_typefecundity:warm_coldcold -0.08 0.07 -0.22 0.06 1.00
## trait_typesurvival:warm_coldcold 0.03 0.05 -0.07 0.14 1.00
## trait_typebody_size:warm_coldwarm -0.01 0.02 -0.05 0.04 1.00
## trait_typefecundity:warm_coldwarm -0.01 0.05 -0.10 0.09 1.00
## trait_typesurvival:warm_coldwarm 0.02 0.04 -0.05 0.09 1.00
## Bulk_ESS Tail_ESS
## trait_typebody_size:warm_coldcold 1848 2500
## trait_typefecundity:warm_coldcold 3849 5593
## trait_typesurvival:warm_coldcold 3599 4514
## trait_typebody_size:warm_coldwarm 2375 3035
## trait_typefecundity:warm_coldwarm 3592 4745
## trait_typesurvival:warm_coldwarm 3239 4369
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnRR_model_warm_cold, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(
# Simplify `trait_type` and `warm_cold` interactions
Parameter = gsub("trait_type([a-z_]+):warm_cold(cold|warm)", "\\1(\\2)", Parameter)
) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~ round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size(cold) | 0.014 | -0.036 | 0.066 |
| fecundity(cold) | -0.080 | -0.222 | 0.056 |
| survival(cold) | 0.030 | -0.074 | 0.139 |
| body_size(warm) | -0.006 | -0.051 | 0.036 |
| fecundity(warm) | -0.006 | -0.097 | 0.089 |
| survival(warm) | 0.022 | -0.050 | 0.093 |
Contrasts
# Generate predictions
emms <- emmeans(lnRR_model_warm_cold, specs = ~warm_cold | trait_type)
# Generate contrasts
contrast(emms, method = "pairwise")
## trait_type = body_size:
## contrast estimate lower.HPD upper.HPD
## cold - warm 0.0205 -0.0114 0.0542
##
## trait_type = fecundity:
## contrast estimate lower.HPD upper.HPD
## cold - warm -0.0731 -0.2010 0.0595
##
## trait_type = survival:
## contrast estimate lower.HPD upper.HPD
## cold - warm 0.0083 -0.0993 0.1137
##
## Point estimate displayed: median
## HPD interval probability: 0.95
Data visualisation
# Generate predictions
emmeans_warm_cold <- as.data.frame(emmeans(lnRR_model_warm_cold,
specs = ~ warm_cold | trait_type))
emmeans_warm_cold$trait_type <- factor(emmeans_warm_cold$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_warm_cold$warm_cold <- factor(emmeans_warm_cold$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Calculate sample sizes and study counts
sample_sizes <- data %>%
group_by(trait_type, warm_cold) %>%
summarise(
estimates = n(),
studies = n_distinct(ref)
)
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75,
lwd=1) +
geom_quasirandom(data = data, # Plot effect sizes, scaled by precision
aes(x = trait_type:warm_cold,
y = lnRR,
fill = trait_type:warm_cold,
size = 1/sqrt(var_lnRR)),
color = "black",
shape = 21,
width = 0.25,
alpha = 0.3) +
geom_errorbar(data = emmeans_warm_cold, # Plot the point estimates in white for background
aes(x = trait_type:warm_cold,
ymin = lower.HPD,
ymax = upper.HPD),
color="white",
size = 2.5,
width = 0.17) +
geom_point(data = emmeans_warm_cold, # Plot the credible intervals in white for background
aes(x = trait_type:warm_cold,
y = emmean),
shape = 21,
size = 4,
stroke = 2,
color="white",
fill = "white") +
geom_errorbar(data = emmeans_warm_cold, # Plot the point estimates
aes(x = trait_type:warm_cold,
ymin = lower.HPD,
ymax = upper.HPD),
color="black",
size = 2,
width = 0.15) +
geom_point(data = emmeans_warm_cold, # Plot the credible intervals
aes(x = trait_type:warm_cold,
y = emmean),
shape = 21,
size = 3.5,
stroke = 2,
color="black",
fill = "white") +
geom_text(data = sample_sizes, # Sample size annotations
aes(x = trait_type:warm_cold,
y = 1.5,
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1, size = 5, show.legend = FALSE) +
theme_bw() + # Customize the plot
labs(y = "lnRR", x = "") +
scale_size_continuous(range = c(2, 8))+
# scale_fill_manual(values = c("#A5F820", "#73B706", "#9CEFFC", "#06A2BA", "#FED2ED", "#B90674"))+
scale_fill_manual(values = c("#06B4BA", "#E80756", "#06B4BA", "#E80756", "#06B4BA", "#E80756"))+
scale_color_manual(values = c("Cold" = "#06B4BA", "Warm" = "#E80756")) + # Text colour
theme(text = element_text(size = 20, color = "black"),
legend.title = ggplot2::element_text(size = 16),
legend.text = ggplot2::element_text(size = 14),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5)) +
guides(fill = "none", size = "none")+
coord_flip() +
ylim(-1.5, 1.5)
ggsave(file = "fig/lnRR_warm_cold.png", width = 12, height = 7, dpi = 500)
Assay temperature difference
We calculated the difference between the assay temperature and control temperature (assay_temp_diff) to account for the fact that animals were tested to a different range of temperatures. We also predicted that effects will vary if traits are assayed at colder- or warmer-than-control conditions, depending on the temperature regime. Because the variation in assay temperature generates some nuisance heterogeneity (sensu Noble et al., 2017), the results of subsequent models were provided after controlling for assay temperature differences. In order words, the results of all moderators are conditional on assay temperature, meaning that results are interpretable as the difference between control and selected lines, when animals are tested at the control temperature.
Model specification
# Model specification
formula <- bf(lnRR ~ 0 + trait_type:warm_cold + trait_type:warm_cold:assay_temp_diff + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnRR)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnRR_model_assay_temp_diff <- brm(formula,
family = gaussian(),
data = data,
data2 = list(phylo_matrix = phylo_matrix,
VCV_lnRR = VCV_lnRR),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnRR_model_assay_temp_diff, file = "RData/lnRR_model_assay_temp_diff.rds")
Model output
# Load model
lnRR_model_assay_temp_diff <- readRDS("RData/lnRR_model_assay_temp_diff.rds")
# Display model output
summary(lnRR_model_assay_temp_diff)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnRR ~ 0 + trait_type:warm_cold + trait_type:warm_cold:assay_temp_diff + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnRR)
## Data: data (Number of observations: 476)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 88)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.02 0.01 0.00 0.04 1.01 836 1101
##
## ~obs (Number of levels: 476)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.05 0.00 0.04 0.06 1.00 1704 3138
##
## ~phylogeny (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.02 0.02 0.00 0.07 1.00 2385 3776
##
## ~ref (Number of levels: 44)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.01 0.01 0.00
## sd(trait_typefecundity) 0.21 0.04 0.15
## sd(trait_typesurvival) 0.13 0.03 0.08
## cor(trait_typebody_size,trait_typefecundity) -0.13 0.46 -0.89
## cor(trait_typebody_size,trait_typesurvival) 0.08 0.49 -0.84
## cor(trait_typefecundity,trait_typesurvival) -0.07 0.52 -0.90
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.02 1.00 1547 3228
## sd(trait_typefecundity) 0.30 1.00 3481 5702
## sd(trait_typesurvival) 0.20 1.00 3243 5184
## cor(trait_typebody_size,trait_typefecundity) 0.77 1.01 426 1075
## cor(trait_typebody_size,trait_typesurvival) 0.89 1.00 862 2032
## cor(trait_typefecundity,trait_typesurvival) 0.83 1.01 799 2678
##
## ~species (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.02 0.01 0.00 0.04 1.00 1970 3307
##
## Regression Coefficients:
## Estimate Est.Error l-95% CI
## trait_typebody_size:warm_coldcold 0.02 0.02 -0.02
## trait_typefecundity:warm_coldcold -0.17 0.06 -0.30
## trait_typesurvival:warm_coldcold -0.06 0.06 -0.17
## trait_typebody_size:warm_coldwarm -0.00 0.02 -0.04
## trait_typefecundity:warm_coldwarm -0.04 0.05 -0.13
## trait_typesurvival:warm_coldwarm -0.01 0.04 -0.09
## trait_typebody_size:warm_coldcold:assay_temp_diff 0.00 0.00 -0.00
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.06 0.01 -0.07
## trait_typesurvival:warm_coldcold:assay_temp_diff -0.04 0.00 -0.05
## trait_typebody_size:warm_coldwarm:assay_temp_diff -0.00 0.00 -0.00
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.00 0.00 -0.00
## trait_typesurvival:warm_coldwarm:assay_temp_diff 0.00 0.00 -0.00
## u-95% CI Rhat Bulk_ESS
## trait_typebody_size:warm_coldcold 0.07 1.00 2228
## trait_typefecundity:warm_coldcold -0.04 1.00 3618
## trait_typesurvival:warm_coldcold 0.05 1.00 3585
## trait_typebody_size:warm_coldwarm 0.04 1.00 2664
## trait_typefecundity:warm_coldwarm 0.06 1.00 3070
## trait_typesurvival:warm_coldwarm 0.06 1.00 3114
## trait_typebody_size:warm_coldcold:assay_temp_diff 0.01 1.00 2104
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.05 1.00 4502
## trait_typesurvival:warm_coldcold:assay_temp_diff -0.03 1.00 3516
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.00 1.00 3803
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.01 1.00 3198
## trait_typesurvival:warm_coldwarm:assay_temp_diff 0.01 1.00 4448
## Tail_ESS
## trait_typebody_size:warm_coldcold 3044
## trait_typefecundity:warm_coldcold 4811
## trait_typesurvival:warm_coldcold 5338
## trait_typebody_size:warm_coldwarm 2969
## trait_typefecundity:warm_coldwarm 4633
## trait_typesurvival:warm_coldwarm 4161
## trait_typebody_size:warm_coldcold:assay_temp_diff 3599
## trait_typefecundity:warm_coldcold:assay_temp_diff 6330
## trait_typesurvival:warm_coldcold:assay_temp_diff 5524
## trait_typebody_size:warm_coldwarm:assay_temp_diff 4949
## trait_typefecundity:warm_coldwarm:assay_temp_diff 5467
## trait_typesurvival:warm_coldwarm:assay_temp_diff 5841
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnRR_model_assay_temp_diff, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(Parameter = gsub("trait_type([a-z_]+):warm_cold(cold|warm)", "\\1(\\2)",
Parameter), Parameter = gsub("trait_type([a-z_]+)\\(warm\\):assay_temp_diff",
"\\1(warm): assay_temp_diff", Parameter)) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size(cold) | 0.022 | -0.022 | 0.067 |
| fecundity(cold) | -0.167 | -0.295 | -0.042 |
| survival(cold) | -0.062 | -0.166 | 0.052 |
| body_size(warm) | -0.001 | -0.042 | 0.041 |
| fecundity(warm) | -0.036 | -0.126 | 0.056 |
| survival(warm) | -0.010 | -0.087 | 0.062 |
| body_size(cold):assay_temp_diff | 0.001 | -0.004 | 0.006 |
| fecundity(cold):assay_temp_diff | -0.062 | -0.073 | -0.052 |
| survival(cold):assay_temp_diff | -0.044 | -0.053 | -0.034 |
| body_size(warm):assay_temp_diff | -0.001 | -0.004 | 0.002 |
| fecundity(warm):assay_temp_diff | 0.004 | -0.003 | 0.011 |
| survival(warm):assay_temp_diff | 0.005 | -0.002 | 0.011 |
Data visualisation
# Generate predictions
emmeans_assay_temp_diff <- as.data.frame(emmeans(
lnRR_model_assay_temp_diff,
specs = ~ assay_temp_diff | trait_type * warm_cold,
at = list(assay_temp_diff = seq(min(data$assay_temp_diff), max(data$assay_temp_diff), by = 0.5)))
)
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_assay_temp_diff = min(assay_temp_diff),
max_assay_temp_diff = max(assay_temp_diff)
)
emmeans_assay_temp_diff$trait_type <- factor(emmeans_assay_temp_diff$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_assay_temp_diff$warm_cold <- factor(emmeans_assay_temp_diff$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_assay_temp_diff <- emmeans_assay_temp_diff %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
assay_temp_diff >= min_assay_temp_diff,
assay_temp_diff <= max_assay_temp_diff
)
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_vline(xintercept = 0, # Vertical line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = assay_temp_diff,
y = lnRR,
size = 1/sqrt(var_lnRR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_assay_temp_diff, # Shaded area for credible intervals
aes(x = assay_temp_diff,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_assay_temp_diff, # Predicted regression line
aes(y = emmean,
x = assay_temp_diff,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -0.9, -1.3), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Assay temperature difference", y = "lnRR", col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 25, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 22)) +
guides(fill = "none", size = "none")+
ylim(-1.5, 1.5)
ggsave(file = "fig/lnRR_assay_temp_diff.png", width = 12, height = 10, dpi = 500)
Selection temperature
Model specification
# Model specification
formula <- bf(lnRR ~ 0 + trait_type:warm_cold +
trait_type:warm_cold:assay_temp_diff +
trait_type:warm_cold:select_temp_diff + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnRR)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnRR_model_sel_temp_diff <- brm(formula,
family = gaussian(),
data = data,
data2 = list(phylo_matrix = phylo_matrix,
VCV_lnRR = VCV_lnRR),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnRR_model_sel_temp_diff, file = "RData/lnRR_model_sel_temp_diff.rds")
Model output
# Load model
lnRR_model_sel_temp_diff <- readRDS("RData/lnRR_model_sel_temp_diff.rds")
# Display model output
summary(lnRR_model_sel_temp_diff)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnRR ~ 0 + trait_type:warm_cold + trait_type:warm_cold:assay_temp_diff + trait_type:warm_cold:select_temp_diff + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnRR)
## Data: data (Number of observations: 476)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 88)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.02 0.01 0.00 0.04 1.02 496 672
##
## ~obs (Number of levels: 476)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.05 0.00 0.04 0.06 1.00 1440 2615
##
## ~phylogeny (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.02 0.02 0.00 0.07 1.00 1936 2888
##
## ~ref (Number of levels: 44)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.01 0.01 0.00
## sd(trait_typefecundity) 0.22 0.04 0.16
## sd(trait_typesurvival) 0.13 0.03 0.08
## cor(trait_typebody_size,trait_typefecundity) -0.19 0.45 -0.90
## cor(trait_typebody_size,trait_typesurvival) 0.19 0.48 -0.80
## cor(trait_typefecundity,trait_typesurvival) -0.40 0.53 -0.98
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.03 1.00 1232 2854
## sd(trait_typefecundity) 0.31 1.00 3355 4947
## sd(trait_typesurvival) 0.19 1.00 3672 5213
## cor(trait_typebody_size,trait_typefecundity) 0.75 1.02 429 825
## cor(trait_typebody_size,trait_typesurvival) 0.91 1.00 744 1327
## cor(trait_typefecundity,trait_typesurvival) 0.75 1.00 766 1965
##
## ~species (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.02 0.01 0.00 0.04 1.00 1901 3018
##
## Regression Coefficients:
## Estimate Est.Error l-95% CI
## trait_typebody_size:warm_coldcold 0.02 0.03 -0.05
## trait_typefecundity:warm_coldcold -0.05 0.14 -0.31
## trait_typesurvival:warm_coldcold 0.35 0.20 -0.03
## trait_typebody_size:warm_coldwarm 0.01 0.03 -0.04
## trait_typefecundity:warm_coldwarm -0.02 0.07 -0.16
## trait_typesurvival:warm_coldwarm -0.03 0.07 -0.16
## trait_typebody_size:warm_coldcold:assay_temp_diff 0.00 0.00 -0.00
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.06 0.01 -0.07
## trait_typesurvival:warm_coldcold:assay_temp_diff -0.05 0.00 -0.05
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.00 0.00 -0.00
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.00 0.00 -0.00
## trait_typesurvival:warm_coldwarm:assay_temp_diff 0.00 0.00 -0.00
## trait_typebody_size:warm_coldcold:select_temp_diff 0.00 0.00 -0.01
## trait_typefecundity:warm_coldcold:select_temp_diff -0.02 0.02 -0.06
## trait_typesurvival:warm_coldcold:select_temp_diff -0.06 0.03 -0.12
## trait_typebody_size:warm_coldwarm:select_temp_diff -0.00 0.00 -0.01
## trait_typefecundity:warm_coldwarm:select_temp_diff 0.00 0.01 -0.02
## trait_typesurvival:warm_coldwarm:select_temp_diff 0.01 0.01 -0.01
## u-95% CI Rhat Bulk_ESS
## trait_typebody_size:warm_coldcold 0.08 1.00 1186
## trait_typefecundity:warm_coldcold 0.22 1.00 3406
## trait_typesurvival:warm_coldcold 0.75 1.00 2733
## trait_typebody_size:warm_coldwarm 0.06 1.00 2179
## trait_typefecundity:warm_coldwarm 0.11 1.00 2379
## trait_typesurvival:warm_coldwarm 0.11 1.00 1635
## trait_typebody_size:warm_coldcold:assay_temp_diff 0.01 1.00 1615
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.05 1.00 4516
## trait_typesurvival:warm_coldcold:assay_temp_diff -0.04 1.00 2879
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.00 1.00 2302
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.01 1.00 3142
## trait_typesurvival:warm_coldwarm:assay_temp_diff 0.01 1.00 4091
## trait_typebody_size:warm_coldcold:select_temp_diff 0.01 1.00 1341
## trait_typefecundity:warm_coldcold:select_temp_diff 0.02 1.00 3502
## trait_typesurvival:warm_coldcold:select_temp_diff -0.01 1.00 2614
## trait_typebody_size:warm_coldwarm:select_temp_diff 0.00 1.00 2142
## trait_typefecundity:warm_coldwarm:select_temp_diff 0.02 1.00 2704
## trait_typesurvival:warm_coldwarm:select_temp_diff 0.02 1.00 1990
## Tail_ESS
## trait_typebody_size:warm_coldcold 2116
## trait_typefecundity:warm_coldcold 5034
## trait_typesurvival:warm_coldcold 5062
## trait_typebody_size:warm_coldwarm 2989
## trait_typefecundity:warm_coldwarm 4122
## trait_typesurvival:warm_coldwarm 3197
## trait_typebody_size:warm_coldcold:assay_temp_diff 2793
## trait_typefecundity:warm_coldcold:assay_temp_diff 5598
## trait_typesurvival:warm_coldcold:assay_temp_diff 4940
## trait_typebody_size:warm_coldwarm:assay_temp_diff 4053
## trait_typefecundity:warm_coldwarm:assay_temp_diff 4707
## trait_typesurvival:warm_coldwarm:assay_temp_diff 4931
## trait_typebody_size:warm_coldcold:select_temp_diff 2795
## trait_typefecundity:warm_coldcold:select_temp_diff 4593
## trait_typesurvival:warm_coldcold:select_temp_diff 4371
## trait_typebody_size:warm_coldwarm:select_temp_diff 4514
## trait_typefecundity:warm_coldwarm:select_temp_diff 4257
## trait_typesurvival:warm_coldwarm:select_temp_diff 3936
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnRR_model_sel_temp_diff, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(Parameter = gsub("trait_type([a-z_]+):warm_cold(cold|warm)", "\\1(\\2)",
Parameter), Parameter = gsub("trait_type([a-z_]+)\\(warm\\):select_temp_diff",
"\\1(warm): select_temp_diff", Parameter)) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size(cold) | 0.018 | -0.049 | 0.084 |
| fecundity(cold) | -0.048 | -0.313 | 0.222 |
| survival(cold) | 0.349 | -0.033 | 0.746 |
| body_size(warm) | 0.008 | -0.044 | 0.055 |
| fecundity(warm) | -0.024 | -0.157 | 0.115 |
| survival(warm) | -0.029 | -0.162 | 0.108 |
| body_size(cold):assay_temp_diff | 0.001 | -0.004 | 0.006 |
| fecundity(cold):assay_temp_diff | -0.063 | -0.073 | -0.052 |
| survival(cold):assay_temp_diff | -0.045 | -0.055 | -0.036 |
| body_size(warm):assay_temp_diff | 0.000 | -0.004 | 0.004 |
| fecundity(warm):assay_temp_diff | 0.004 | -0.003 | 0.011 |
| survival(warm):assay_temp_diff | 0.004 | -0.003 | 0.011 |
| body_size(cold):select_temp_diff | 0.000 | -0.009 | 0.009 |
| fecundity(cold):select_temp_diff | -0.022 | -0.064 | 0.020 |
| survival(cold):select_temp_diff | -0.060 | -0.116 | -0.006 |
| body_size(warm):select_temp_diff | -0.002 | -0.008 | 0.004 |
| fecundity(warm):select_temp_diff | 0.000 | -0.019 | 0.019 |
| survival(warm):select_temp_diff | 0.006 | -0.014 | 0.025 |
Data visualisation
# Generate predictions
emmeans_sel_temp_diff <- as.data.frame(emmeans(
lnRR_model_sel_temp_diff,
specs = ~ select_temp_diff | trait_type * warm_cold,
at = list(select_temp_diff = seq(min(data$select_temp_diff), max(data$select_temp_diff), by = 0.5),
assay_temp_diff = 0))
) # Conditional effects on assay_temp_diff = 0
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_select_temp_diff = min(select_temp_diff),
max_select_temp_diff = max(select_temp_diff)
)
emmeans_sel_temp_diff$trait_type <- factor(emmeans_sel_temp_diff$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_sel_temp_diff$warm_cold <- factor(emmeans_sel_temp_diff$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_sel_temp_diff <- emmeans_sel_temp_diff %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
select_temp_diff >= min_select_temp_diff,
select_temp_diff <= max_select_temp_diff
)
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = select_temp_diff,
y = lnRR,
size = 1/sqrt(var_lnRR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_sel_temp_diff, # Shaded area for credible intervals
aes(x = select_temp_diff,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_sel_temp_diff, # Predicted regression line
aes(y = emmean,
x = select_temp_diff,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -0.9, -1.3), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Selection temperature difference", y = "lnRR", col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 25, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 22)) +
guides(fill = "none", size = "none")+
ylim(-1.5, 1.5)
ggsave(file = "fig/lnRR_sel_temp_diff.png", width = 12, height = 7, dpi = 500)
Number of generations of selection
Model specification
# Model specification
formula <- bf(lnRR ~ 0 + trait_type:warm_cold +
trait_type:warm_cold:assay_temp_diff +
trait_type:warm_cold:scale(gen_selection) + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnRR)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnRR_model_gen_selection <- brm(formula,
family = gaussian(),
data = data,
data2 = list(phylo_matrix = phylo_matrix,
VCV_lnRR = VCV_lnRR),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnRR_model_gen_selection, file = "RData/lnRR_model_gen_selection.rds")
Model output
# Load model
lnRR_model_gen_selection <- readRDS("RData/lnRR_model_gen_selection.rds")
# Display model output
summary(lnRR_model_gen_selection)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnRR ~ 0 + trait_type:warm_cold + trait_type:warm_cold:assay_temp_diff + trait_type:warm_cold:scale(gen_selection) + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnRR)
## Data: data (Number of observations: 476)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 88)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.02 0.01 0.00 0.04 1.01 617 776
##
## ~obs (Number of levels: 476)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.05 0.00 0.04 0.06 1.00 1605 2480
##
## ~phylogeny (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.03 0.02 0.00 0.07 1.00 2219 3539
##
## ~ref (Number of levels: 44)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.01 0.01 0.00
## sd(trait_typefecundity) 0.21 0.04 0.15
## sd(trait_typesurvival) 0.13 0.03 0.08
## cor(trait_typebody_size,trait_typefecundity) -0.09 0.46 -0.87
## cor(trait_typebody_size,trait_typesurvival) 0.01 0.50 -0.89
## cor(trait_typefecundity,trait_typesurvival) 0.21 0.45 -0.73
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.03 1.00 1513 2634
## sd(trait_typefecundity) 0.30 1.00 3039 5427
## sd(trait_typesurvival) 0.20 1.00 3835 5162
## cor(trait_typebody_size,trait_typefecundity) 0.81 1.01 443 913
## cor(trait_typebody_size,trait_typesurvival) 0.87 1.00 740 1751
## cor(trait_typefecundity,trait_typesurvival) 0.88 1.00 1349 2888
##
## ~species (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.02 0.01 0.00 0.04 1.00 1886 3491
##
## Regression Coefficients:
## Estimate Est.Error
## trait_typebody_size:warm_coldcold 0.02 0.02
## trait_typefecundity:warm_coldcold -0.01 0.09
## trait_typesurvival:warm_coldcold -0.07 0.08
## trait_typebody_size:warm_coldwarm -0.00 0.02
## trait_typefecundity:warm_coldwarm -0.04 0.05
## trait_typesurvival:warm_coldwarm -0.03 0.05
## trait_typebody_size:warm_coldcold:assay_temp_diff 0.00 0.00
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.06 0.01
## trait_typesurvival:warm_coldcold:assay_temp_diff -0.05 0.00
## trait_typebody_size:warm_coldwarm:assay_temp_diff -0.00 0.00
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.00 0.00
## trait_typesurvival:warm_coldwarm:assay_temp_diff 0.00 0.00
## trait_typebody_size:warm_coldcold:scalegen_selection 0.01 0.01
## trait_typefecundity:warm_coldcold:scalegen_selection -0.16 0.06
## trait_typesurvival:warm_coldcold:scalegen_selection -0.05 0.05
## trait_typebody_size:warm_coldwarm:scalegen_selection 0.00 0.01
## trait_typefecundity:warm_coldwarm:scalegen_selection -0.01 0.05
## trait_typesurvival:warm_coldwarm:scalegen_selection -0.09 0.07
## l-95% CI u-95% CI Rhat
## trait_typebody_size:warm_coldcold -0.03 0.06 1.00
## trait_typefecundity:warm_coldcold -0.20 0.17 1.00
## trait_typesurvival:warm_coldcold -0.22 0.09 1.00
## trait_typebody_size:warm_coldwarm -0.05 0.04 1.00
## trait_typefecundity:warm_coldwarm -0.13 0.05 1.00
## trait_typesurvival:warm_coldwarm -0.12 0.06 1.00
## trait_typebody_size:warm_coldcold:assay_temp_diff -0.00 0.01 1.00
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.07 -0.05 1.00
## trait_typesurvival:warm_coldcold:assay_temp_diff -0.05 -0.04 1.00
## trait_typebody_size:warm_coldwarm:assay_temp_diff -0.00 0.00 1.00
## trait_typefecundity:warm_coldwarm:assay_temp_diff -0.00 0.01 1.00
## trait_typesurvival:warm_coldwarm:assay_temp_diff -0.00 0.01 1.00
## trait_typebody_size:warm_coldcold:scalegen_selection -0.01 0.02 1.00
## trait_typefecundity:warm_coldcold:scalegen_selection -0.28 -0.05 1.00
## trait_typesurvival:warm_coldcold:scalegen_selection -0.15 0.05 1.00
## trait_typebody_size:warm_coldwarm:scalegen_selection -0.01 0.01 1.00
## trait_typefecundity:warm_coldwarm:scalegen_selection -0.11 0.08 1.00
## trait_typesurvival:warm_coldwarm:scalegen_selection -0.23 0.04 1.00
## Bulk_ESS Tail_ESS
## trait_typebody_size:warm_coldcold 2264 3007
## trait_typefecundity:warm_coldcold 4049 5260
## trait_typesurvival:warm_coldcold 3567 4741
## trait_typebody_size:warm_coldwarm 2396 2621
## trait_typefecundity:warm_coldwarm 2960 4425
## trait_typesurvival:warm_coldwarm 3207 4061
## trait_typebody_size:warm_coldcold:assay_temp_diff 2027 3280
## trait_typefecundity:warm_coldcold:assay_temp_diff 5125 5341
## trait_typesurvival:warm_coldcold:assay_temp_diff 3387 5279
## trait_typebody_size:warm_coldwarm:assay_temp_diff 3289 5032
## trait_typefecundity:warm_coldwarm:assay_temp_diff 3203 5785
## trait_typesurvival:warm_coldwarm:assay_temp_diff 5423 5804
## trait_typebody_size:warm_coldcold:scalegen_selection 2243 3575
## trait_typefecundity:warm_coldcold:scalegen_selection 4317 4963
## trait_typesurvival:warm_coldcold:scalegen_selection 2591 4399
## trait_typebody_size:warm_coldwarm:scalegen_selection 3682 4568
## trait_typefecundity:warm_coldwarm:scalegen_selection 4275 5172
## trait_typesurvival:warm_coldwarm:scalegen_selection 4296 5586
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnRR_model_gen_selection, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(
# Simplify `trait_type` and `warm_cold` interactions
Parameter = gsub("trait_type([a-z_]+):warm_cold(cold|warm)", "\\1(\\2)", Parameter),
# Adjust for assay_temp_diff
Parameter = gsub("trait_type([a-z_]+)\\((cold|warm)\\):assay_temp_diff", "\\1(\\2): assay_temp_diff", Parameter),
# Remove "scale" from gen_selection
Parameter = gsub("scalegen_selection", "gen_selection", Parameter)
) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~ round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size(cold) | 0.015 | -0.031 | 0.061 |
| fecundity(cold) | -0.012 | -0.199 | 0.174 |
| survival(cold) | -0.067 | -0.220 | 0.087 |
| body_size(warm) | -0.005 | -0.051 | 0.037 |
| fecundity(warm) | -0.040 | -0.134 | 0.053 |
| survival(warm) | -0.028 | -0.121 | 0.062 |
| body_size(cold):assay_temp_diff | 0.001 | -0.003 | 0.006 |
| fecundity(cold):assay_temp_diff | -0.063 | -0.073 | -0.052 |
| survival(cold):assay_temp_diff | -0.045 | -0.055 | -0.035 |
| body_size(warm):assay_temp_diff | -0.001 | -0.004 | 0.003 |
| fecundity(warm):assay_temp_diff | 0.003 | -0.004 | 0.011 |
| survival(warm):assay_temp_diff | 0.004 | -0.002 | 0.011 |
| body_size(cold):gen_selection | 0.005 | -0.012 | 0.023 |
| fecundity(cold):gen_selection | -0.163 | -0.283 | -0.050 |
| survival(cold):gen_selection | -0.048 | -0.146 | 0.051 |
| body_size(warm):gen_selection | 0.001 | -0.010 | 0.011 |
| fecundity(warm):gen_selection | -0.014 | -0.112 | 0.084 |
| survival(warm):gen_selection | -0.090 | -0.233 | 0.045 |
Data visualisation
# Generate predictions
emmeans_gen_selection <- as.data.frame(emmeans(
lnRR_model_gen_selection,
specs = ~ gen_selection | trait_type * warm_cold,
at = list(gen_selection = seq(min(data$gen_selection), max(data$gen_selection), by = 0.5),
assay_temp_diff = 0))
) # Conditional effects on assay_temp_diff = 0
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_gen_selection = min(gen_selection),
max_gen_selection = max(gen_selection)
)
emmeans_gen_selection$trait_type <- factor(emmeans_gen_selection$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_gen_selection$warm_cold <- factor(emmeans_gen_selection$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_gen_selection <- emmeans_gen_selection %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
gen_selection >= min_gen_selection,
gen_selection <= max_gen_selection
)
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = gen_selection,
y = lnRR,
size = 1/sqrt(var_lnRR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_gen_selection, # Shaded area for credible intervals
aes(x = gen_selection,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_gen_selection, # Predicted regression line
aes(y = emmean,
x = gen_selection,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -0.9, -1.3), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Number of generations of selection", y = "lnRR", col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 25, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 22)) +
guides(fill = "none", size = "none")+
ylim(-1.5, 1.5)+
xlim(0, 150)
ggsave(file = "fig/lnRR_gen_selection.png", width = 12, height = 7, dpi = 500)
Number of generations of common garden
Model specification
# Model specification
formula <- bf(lnRR ~ 0 + trait_type:warm_cold +
trait_type:warm_cold:assay_temp_diff +
trait_type:warm_cold:scale(gen_common_garden) + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnRR)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnRR_model_gen_common_garden <- brm(formula,
family = gaussian(),
data = data,
data2 = list(phylo_matrix = phylo_matrix,
VCV_lnRR = VCV_lnRR),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnRR_model_gen_common_garden, file = "RData/lnRR_model_gen_common_garden.rds")
Model output
# Load model
lnRR_model_gen_common_garden <- readRDS("RData/lnRR_model_gen_common_garden.rds")
# Display model output
summary(lnRR_model_gen_common_garden)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnRR ~ 0 + trait_type:warm_cold + trait_type:warm_cold:assay_temp_diff + trait_type:warm_cold:scale(gen_common_garden) + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnRR)
## Data: data (Number of observations: 476)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 88)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.02 0.01 0.00 0.04 1.01 733 688
##
## ~obs (Number of levels: 476)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.05 0.00 0.04 0.06 1.00 1435 3238
##
## ~phylogeny (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.03 0.02 0.00 0.07 1.00 2223 3481
##
## ~ref (Number of levels: 44)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.01 0.01 0.00
## sd(trait_typefecundity) 0.20 0.03 0.14
## sd(trait_typesurvival) 0.50 0.21 0.14
## cor(trait_typebody_size,trait_typefecundity) -0.14 0.44 -0.89
## cor(trait_typebody_size,trait_typesurvival) 0.10 0.48 -0.83
## cor(trait_typefecundity,trait_typesurvival) -0.48 0.34 -0.92
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.03 1.00 1642 3193
## sd(trait_typefecundity) 0.28 1.00 3616 5629
## sd(trait_typesurvival) 0.95 1.01 578 988
## cor(trait_typebody_size,trait_typefecundity) 0.78 1.01 438 782
## cor(trait_typebody_size,trait_typesurvival) 0.89 1.01 579 1488
## cor(trait_typefecundity,trait_typesurvival) 0.33 1.00 1661 3238
##
## ~species (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.02 0.01 0.00 0.04 1.00 2154 3219
##
## Regression Coefficients:
## Estimate Est.Error
## trait_typebody_size:warm_coldcold 0.02 0.02
## trait_typefecundity:warm_coldcold -0.17 0.06
## trait_typesurvival:warm_coldcold 0.12 0.13
## trait_typebody_size:warm_coldwarm -0.00 0.02
## trait_typefecundity:warm_coldwarm -0.04 0.05
## trait_typesurvival:warm_coldwarm 0.10 0.12
## trait_typebody_size:warm_coldcold:assay_temp_diff 0.00 0.00
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.06 0.01
## trait_typesurvival:warm_coldcold:assay_temp_diff -0.05 0.00
## trait_typebody_size:warm_coldwarm:assay_temp_diff -0.00 0.00
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.00 0.00
## trait_typesurvival:warm_coldwarm:assay_temp_diff 0.00 0.00
## trait_typebody_size:warm_coldcold:scalegen_common_garden -0.04 0.06
## trait_typefecundity:warm_coldcold:scalegen_common_garden 0.36 0.24
## trait_typesurvival:warm_coldcold:scalegen_common_garden 1.84 0.80
## trait_typebody_size:warm_coldwarm:scalegen_common_garden 0.03 0.05
## trait_typefecundity:warm_coldwarm:scalegen_common_garden 0.02 0.01
## trait_typesurvival:warm_coldwarm:scalegen_common_garden 2.07 0.77
## l-95% CI u-95% CI Rhat
## trait_typebody_size:warm_coldcold -0.03 0.06 1.00
## trait_typefecundity:warm_coldcold -0.29 -0.05 1.00
## trait_typesurvival:warm_coldcold -0.11 0.42 1.00
## trait_typebody_size:warm_coldwarm -0.05 0.04 1.00
## trait_typefecundity:warm_coldwarm -0.13 0.05 1.00
## trait_typesurvival:warm_coldwarm -0.12 0.35 1.00
## trait_typebody_size:warm_coldcold:assay_temp_diff -0.00 0.01 1.00
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.07 -0.05 1.00
## trait_typesurvival:warm_coldcold:assay_temp_diff -0.05 -0.04 1.00
## trait_typebody_size:warm_coldwarm:assay_temp_diff -0.00 0.00 1.00
## trait_typefecundity:warm_coldwarm:assay_temp_diff -0.00 0.01 1.00
## trait_typesurvival:warm_coldwarm:assay_temp_diff -0.00 0.01 1.00
## trait_typebody_size:warm_coldcold:scalegen_common_garden -0.15 0.07 1.00
## trait_typefecundity:warm_coldcold:scalegen_common_garden -0.12 0.84 1.00
## trait_typesurvival:warm_coldcold:scalegen_common_garden 0.29 3.30 1.01
## trait_typebody_size:warm_coldwarm:scalegen_common_garden -0.06 0.13 1.00
## trait_typefecundity:warm_coldwarm:scalegen_common_garden -0.01 0.05 1.00
## trait_typesurvival:warm_coldwarm:scalegen_common_garden 0.56 3.46 1.01
## Bulk_ESS Tail_ESS
## trait_typebody_size:warm_coldcold 2459 2892
## trait_typefecundity:warm_coldcold 3326 4513
## trait_typesurvival:warm_coldcold 1766 3486
## trait_typebody_size:warm_coldwarm 2575 2515
## trait_typefecundity:warm_coldwarm 2905 4016
## trait_typesurvival:warm_coldwarm 2261 3265
## trait_typebody_size:warm_coldcold:assay_temp_diff 2321 3484
## trait_typefecundity:warm_coldcold:assay_temp_diff 5260 5568
## trait_typesurvival:warm_coldcold:assay_temp_diff 3092 3982
## trait_typebody_size:warm_coldwarm:assay_temp_diff 3653 4438
## trait_typefecundity:warm_coldwarm:assay_temp_diff 3689 5846
## trait_typesurvival:warm_coldwarm:assay_temp_diff 4614 5578
## trait_typebody_size:warm_coldcold:scalegen_common_garden 3148 4107
## trait_typefecundity:warm_coldcold:scalegen_common_garden 7731 5761
## trait_typesurvival:warm_coldcold:scalegen_common_garden 655 1522
## trait_typebody_size:warm_coldwarm:scalegen_common_garden 2303 3862
## trait_typefecundity:warm_coldwarm:scalegen_common_garden 5577 4857
## trait_typesurvival:warm_coldwarm:scalegen_common_garden 628 1296
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnRR_model_gen_common_garden, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(
# Simplify `trait_type` and `warm_cold` interactions
Parameter = gsub("trait_type([a-z_]+):warm_cold(cold|warm)", "\\1(\\2)", Parameter),
# Adjust for assay_temp_diff
Parameter = gsub("trait_type([a-z_]+)\\((cold|warm)\\):assay_temp_diff", "\\1(\\2): assay_temp_diff", Parameter),
# Remove "scale" from gen_common_garden
Parameter = gsub("scalegen_common_garden", "gen_common_garden", Parameter)
) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~ round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size(cold) | 0.016 | -0.030 | 0.061 |
| fecundity(cold) | -0.170 | -0.295 | -0.048 |
| survival(cold) | 0.125 | -0.107 | 0.416 |
| body_size(warm) | -0.003 | -0.049 | 0.039 |
| fecundity(warm) | -0.043 | -0.133 | 0.047 |
| survival(warm) | 0.097 | -0.118 | 0.354 |
| body_size(cold):assay_temp_diff | 0.001 | -0.003 | 0.006 |
| fecundity(cold):assay_temp_diff | -0.062 | -0.073 | -0.052 |
| survival(cold):assay_temp_diff | -0.045 | -0.055 | -0.036 |
| body_size(warm):assay_temp_diff | 0.000 | -0.004 | 0.003 |
| fecundity(warm):assay_temp_diff | 0.004 | -0.003 | 0.012 |
| survival(warm):assay_temp_diff | 0.002 | -0.005 | 0.010 |
| body_size(cold):gen_common_garden | -0.042 | -0.150 | 0.073 |
| fecundity(cold):gen_common_garden | 0.357 | -0.118 | 0.839 |
| survival(cold):gen_common_garden | 1.836 | 0.292 | 3.301 |
| body_size(warm):gen_common_garden | 0.031 | -0.060 | 0.128 |
| fecundity(warm):gen_common_garden | 0.018 | -0.011 | 0.046 |
| survival(warm):gen_common_garden | 2.069 | 0.558 | 3.455 |
Data visualisation
# Generate predictions
emmeans_gen_common_garden <- as.data.frame(emmeans(
lnRR_model_gen_common_garden,
specs = ~ gen_common_garden | trait_type * warm_cold,
at = list(gen_common_garden = seq(min(data$gen_common_garden), max(data$gen_common_garden), by = 1),
assay_temp_diff = 0))
) # Conditional effects on assay_temp_diff = 0
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_gen_common_garden = min(gen_common_garden),
max_gen_common_garden = max(gen_common_garden)
)
emmeans_gen_common_garden$trait_type <- factor(emmeans_gen_common_garden$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_gen_common_garden$warm_cold <- factor(emmeans_gen_common_garden$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_gen_common_garden <- emmeans_gen_common_garden %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
gen_common_garden >= min_gen_common_garden,
gen_common_garden <= max_gen_common_garden
)
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = gen_common_garden,
y = lnRR,
size = 1/sqrt(var_lnRR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_gen_common_garden, # Shaded area for credible intervals
aes(x = gen_common_garden,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_gen_common_garden, # Predicted regression line
aes(y = emmean,
x = gen_common_garden,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -0.9, -1.3), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Number of generations of common garden", y = "lnRR", col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 25, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 22)) +
guides(fill = "none", size = "none")+
coord_cartesian(ylim = c(-1.5, 1.5),
xlim = c(0, 12))
ggsave(file = "fig/lnRR_gen_common_garden.png", width = 12, height = 7, dpi = 500)
Population size
Model specification
# Model specification
formula <- bf(lnRR ~ 0 + trait_type:warm_cold +
trait_type:warm_cold:assay_temp_diff +
trait_type:warm_cold:scale(pop_size) + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnRR)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnRR_model_pop_size <- brm(formula,
family = gaussian(),
data = data,
data2 = list(phylo_matrix = phylo_matrix,
VCV_lnRR = VCV_lnRR),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnRR_model_pop_size, file = "RData/lnRR_model_pop_size.rds")
Model output
# Load model
lnRR_model_pop_size <- readRDS("RData/lnRR_model_pop_size.rds")
# Display model output
summary(lnRR_model_pop_size)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnRR ~ 0 + trait_type:warm_cold + trait_type:warm_cold:assay_temp_diff + trait_type:warm_cold:scale(pop_size) + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnRR)
## Data: data (Number of observations: 426)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 79)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.01 0.01 0.00 0.03 1.00 743 1669
##
## ~obs (Number of levels: 426)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.05 0.00 0.04 0.05 1.00 1926 3742
##
## ~phylogeny (Number of levels: 22)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.03 0.02 0.00 0.07 1.00 2664 3599
##
## ~ref (Number of levels: 41)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.01 0.01 0.00
## sd(trait_typefecundity) 0.23 0.04 0.16
## sd(trait_typesurvival) 0.13 0.03 0.08
## cor(trait_typebody_size,trait_typefecundity) -0.07 0.46 -0.88
## cor(trait_typebody_size,trait_typesurvival) 0.18 0.49 -0.81
## cor(trait_typefecundity,trait_typesurvival) 0.02 0.48 -0.83
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.03 1.00 1496 3181
## sd(trait_typefecundity) 0.33 1.00 2900 5491
## sd(trait_typesurvival) 0.21 1.00 3678 4857
## cor(trait_typebody_size,trait_typefecundity) 0.83 1.01 381 878
## cor(trait_typebody_size,trait_typesurvival) 0.92 1.01 904 1910
## cor(trait_typefecundity,trait_typesurvival) 0.85 1.00 1220 2830
##
## ~species (Number of levels: 22)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.02 0.01 0.00 0.04 1.00 2638 3700
##
## Regression Coefficients:
## Estimate Est.Error l-95% CI
## trait_typebody_size:warm_coldcold 0.02 0.02 -0.03
## trait_typefecundity:warm_coldcold -0.25 0.08 -0.41
## trait_typesurvival:warm_coldcold -0.13 0.07 -0.25
## trait_typebody_size:warm_coldwarm -0.00 0.02 -0.05
## trait_typefecundity:warm_coldwarm -0.04 0.05 -0.15
## trait_typesurvival:warm_coldwarm 0.01 0.04 -0.07
## trait_typebody_size:warm_coldcold:assay_temp_diff 0.00 0.00 -0.00
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.06 0.01 -0.07
## trait_typesurvival:warm_coldcold:assay_temp_diff -0.05 0.00 -0.06
## trait_typebody_size:warm_coldwarm:assay_temp_diff -0.00 0.00 -0.00
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.01 0.00 -0.00
## trait_typesurvival:warm_coldwarm:assay_temp_diff 0.00 0.00 -0.00
## trait_typebody_size:warm_coldcold:scalepop_size -0.00 0.01 -0.02
## trait_typefecundity:warm_coldcold:scalepop_size 0.09 0.07 -0.05
## trait_typesurvival:warm_coldcold:scalepop_size 0.12 0.06 0.00
## trait_typebody_size:warm_coldwarm:scalepop_size -0.01 0.01 -0.02
## trait_typefecundity:warm_coldwarm:scalepop_size -0.00 0.05 -0.11
## trait_typesurvival:warm_coldwarm:scalepop_size 0.02 0.03 -0.04
## u-95% CI Rhat Bulk_ESS
## trait_typebody_size:warm_coldcold 0.06 1.00 2728
## trait_typefecundity:warm_coldcold -0.10 1.00 4540
## trait_typesurvival:warm_coldcold 0.01 1.00 4855
## trait_typebody_size:warm_coldwarm 0.04 1.00 2932
## trait_typefecundity:warm_coldwarm 0.07 1.00 3774
## trait_typesurvival:warm_coldwarm 0.09 1.00 3978
## trait_typebody_size:warm_coldcold:assay_temp_diff 0.01 1.00 2115
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.05 1.00 5182
## trait_typesurvival:warm_coldcold:assay_temp_diff -0.04 1.00 4594
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.00 1.00 3215
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.01 1.00 6241
## trait_typesurvival:warm_coldwarm:assay_temp_diff 0.01 1.00 6143
## trait_typebody_size:warm_coldcold:scalepop_size 0.02 1.00 2081
## trait_typefecundity:warm_coldcold:scalepop_size 0.22 1.00 4179
## trait_typesurvival:warm_coldcold:scalepop_size 0.24 1.00 4129
## trait_typebody_size:warm_coldwarm:scalepop_size 0.01 1.00 2585
## trait_typefecundity:warm_coldwarm:scalepop_size 0.10 1.00 4105
## trait_typesurvival:warm_coldwarm:scalepop_size 0.09 1.00 4556
## Tail_ESS
## trait_typebody_size:warm_coldcold 3521
## trait_typefecundity:warm_coldcold 5721
## trait_typesurvival:warm_coldcold 5641
## trait_typebody_size:warm_coldwarm 3273
## trait_typefecundity:warm_coldwarm 5096
## trait_typesurvival:warm_coldwarm 4892
## trait_typebody_size:warm_coldcold:assay_temp_diff 3181
## trait_typefecundity:warm_coldcold:assay_temp_diff 5854
## trait_typesurvival:warm_coldcold:assay_temp_diff 5891
## trait_typebody_size:warm_coldwarm:assay_temp_diff 4866
## trait_typefecundity:warm_coldwarm:assay_temp_diff 5471
## trait_typesurvival:warm_coldwarm:assay_temp_diff 6159
## trait_typebody_size:warm_coldcold:scalepop_size 3983
## trait_typefecundity:warm_coldcold:scalepop_size 4860
## trait_typesurvival:warm_coldcold:scalepop_size 4835
## trait_typebody_size:warm_coldwarm:scalepop_size 3997
## trait_typefecundity:warm_coldwarm:scalepop_size 4570
## trait_typesurvival:warm_coldwarm:scalepop_size 4389
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnRR_model_pop_size, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(
# Simplify `trait_type` and `warm_cold` interactions
Parameter = gsub("trait_type([a-z_]+):warm_cold(cold|warm)", "\\1(\\2)", Parameter),
# Adjust for assay_temp_diff
Parameter = gsub("trait_type([a-z_]+)\\((cold|warm)\\):assay_temp_diff", "\\1(\\2): assay_temp_diff", Parameter),
# Remove "scale" from pop_size
Parameter = gsub("scalepop_size", "pop_size", Parameter)
) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~ round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size(cold) | 0.015 | -0.032 | 0.062 |
| fecundity(cold) | -0.252 | -0.408 | -0.102 |
| survival(cold) | -0.125 | -0.254 | 0.007 |
| body_size(warm) | -0.001 | -0.046 | 0.041 |
| fecundity(warm) | -0.042 | -0.149 | 0.067 |
| survival(warm) | 0.006 | -0.072 | 0.087 |
| body_size(cold):assay_temp_diff | 0.001 | -0.004 | 0.005 |
| fecundity(cold):assay_temp_diff | -0.064 | -0.074 | -0.054 |
| survival(cold):assay_temp_diff | -0.046 | -0.055 | -0.037 |
| body_size(warm):assay_temp_diff | -0.001 | -0.004 | 0.002 |
| fecundity(warm):assay_temp_diff | 0.007 | -0.001 | 0.015 |
| survival(warm):assay_temp_diff | 0.004 | -0.002 | 0.010 |
| body_size(cold):pop_size | 0.000 | -0.019 | 0.019 |
| fecundity(cold):pop_size | 0.085 | -0.048 | 0.221 |
| survival(cold):pop_size | 0.119 | 0.004 | 0.244 |
| body_size(warm):pop_size | -0.007 | -0.021 | 0.008 |
| fecundity(warm):pop_size | -0.001 | -0.106 | 0.098 |
| survival(warm):pop_size | 0.025 | -0.042 | 0.093 |
Data visualisation
# Generate predictions
emmeans_pop_size <- as.data.frame(emmeans(
lnRR_model_pop_size,
specs = ~ pop_size | trait_type * warm_cold,
at = list(pop_size = seq(min(data$pop_size, na.rm=T), max(data$pop_size, na.rm=T), by = 50),
assay_temp_diff = 0))
) # Conditional effects on assay_temp_diff = 0
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_pop_size = min(pop_size, na.rm=T),
max_pop_size = max(pop_size, na.rm=T)
)
emmeans_pop_size$trait_type <- factor(emmeans_pop_size$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_pop_size$warm_cold <- factor(emmeans_pop_size$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_pop_size <- emmeans_pop_size %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
pop_size >= min_pop_size,
pop_size <= max_pop_size
)
# Calculate sample sizes and study counts for population size
sample_sizes_pop_size <- data %>%
filter(is.na(pop_size)==FALSE) %>%
group_by(trait_type, warm_cold) %>%
summarise(
estimates = n(),
studies = n_distinct(ref)
)
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = pop_size,
y = lnRR,
size = 1/sqrt(var_lnRR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_pop_size, # Shaded area for credible intervals
aes(x = pop_size,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_pop_size, # Predicted regression line
aes(y = emmean,
x = pop_size,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes_pop_size,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -0.9, -1.3), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Population size", y = "lnRR", col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 25, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 22)) +
guides(fill = "none", size = "none")+
coord_cartesian(ylim = c(-1.5, 1.5))
ggsave(file = "fig/lnRR_pop_size.png", width = 12, height = 7, dpi = 500)
All moderators
Model specification
# Model specification
formula <- bf(lnRR ~ 0 + trait_type:warm_cold +
trait_type:warm_cold:assay_temp_diff +
trait_type:warm_cold:scale(select_temp_diff) +
trait_type:warm_cold:scale(gen_selection) +
trait_type:warm_cold:scale(gen_common_garden) + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnRR)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnRR_model_all_moderators <- brm(formula,
family = gaussian(),
data = data,
data2 = list(phylo_matrix = phylo_matrix,
VCV_lnRR = VCV_lnRR),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnRR_model_all_moderators, file = "RData/lnRR_model_all_moderators.rds")
Model output
# Load model
lnRR_model_all_moderators <- readRDS("RData/lnRR_model_all_moderators.rds")
# Display model output
summary(lnRR_model_all_moderators)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnRR ~ 0 + trait_type:warm_cold + trait_type:warm_cold:assay_temp_diff + trait_type:warm_cold:scale(select_temp_diff) + trait_type:warm_cold:scale(gen_selection) + trait_type:warm_cold:scale(gen_common_garden) + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnRR)
## Data: data (Number of observations: 476)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 88)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.02 0.01 0.00 0.04 1.00 748 1068
##
## ~obs (Number of levels: 476)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.05 0.00 0.04 0.06 1.00 1779 3050
##
## ~phylogeny (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.03 0.02 0.00 0.07 1.00 2434 3885
##
## ~ref (Number of levels: 44)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.01 0.01 0.00
## sd(trait_typefecundity) 0.21 0.04 0.14
## sd(trait_typesurvival) 0.64 0.21 0.24
## cor(trait_typebody_size,trait_typefecundity) -0.14 0.46 -0.89
## cor(trait_typebody_size,trait_typesurvival) 0.08 0.48 -0.83
## cor(trait_typefecundity,trait_typesurvival) -0.40 0.34 -0.91
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.03 1.00 1776 3327
## sd(trait_typefecundity) 0.30 1.00 3633 5612
## sd(trait_typesurvival) 1.10 1.00 1279 1252
## cor(trait_typebody_size,trait_typefecundity) 0.81 1.02 494 1049
## cor(trait_typebody_size,trait_typesurvival) 0.89 1.01 551 1374
## cor(trait_typefecundity,trait_typesurvival) 0.38 1.00 2284 3612
##
## ~species (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.02 0.01 0.00 0.04 1.00 2569 4362
##
## Regression Coefficients:
## Estimate Est.Error
## trait_typebody_size:warm_coldcold 0.01 0.02
## trait_typefecundity:warm_coldcold -0.04 0.10
## trait_typesurvival:warm_coldcold -0.07 0.29
## trait_typebody_size:warm_coldwarm -0.01 0.02
## trait_typefecundity:warm_coldwarm -0.04 0.05
## trait_typesurvival:warm_coldwarm 0.05 0.18
## trait_typebody_size:warm_coldcold:assay_temp_diff 0.00 0.00
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.06 0.01
## trait_typesurvival:warm_coldcold:assay_temp_diff -0.05 0.00
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.00 0.00
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.00 0.00
## trait_typesurvival:warm_coldwarm:assay_temp_diff 0.00 0.00
## trait_typebody_size:warm_coldcold:scaleselect_temp_diff 0.01 0.02
## trait_typefecundity:warm_coldcold:scaleselect_temp_diff -0.03 0.08
## trait_typesurvival:warm_coldcold:scaleselect_temp_diff -0.29 0.78
## trait_typebody_size:warm_coldwarm:scaleselect_temp_diff -0.01 0.01
## trait_typefecundity:warm_coldwarm:scaleselect_temp_diff 0.01 0.03
## trait_typesurvival:warm_coldwarm:scaleselect_temp_diff 0.01 0.09
## trait_typebody_size:warm_coldcold:scalegen_selection 0.00 0.01
## trait_typefecundity:warm_coldcold:scalegen_selection -0.14 0.07
## trait_typesurvival:warm_coldcold:scalegen_selection 0.30 0.41
## trait_typebody_size:warm_coldwarm:scalegen_selection 0.00 0.01
## trait_typefecundity:warm_coldwarm:scalegen_selection -0.02 0.05
## trait_typesurvival:warm_coldwarm:scalegen_selection -0.22 0.25
## trait_typebody_size:warm_coldcold:scalegen_common_garden -0.05 0.06
## trait_typefecundity:warm_coldcold:scalegen_common_garden 0.11 0.30
## trait_typesurvival:warm_coldcold:scalegen_common_garden 2.89 1.23
## trait_typebody_size:warm_coldwarm:scalegen_common_garden 0.03 0.05
## trait_typefecundity:warm_coldwarm:scalegen_common_garden 0.02 0.02
## trait_typesurvival:warm_coldwarm:scalegen_common_garden 2.56 0.67
## l-95% CI u-95% CI Rhat
## trait_typebody_size:warm_coldcold -0.03 0.06 1.00
## trait_typefecundity:warm_coldcold -0.23 0.15 1.00
## trait_typesurvival:warm_coldcold -0.66 0.51 1.00
## trait_typebody_size:warm_coldwarm -0.06 0.04 1.00
## trait_typefecundity:warm_coldwarm -0.14 0.06 1.00
## trait_typesurvival:warm_coldwarm -0.31 0.43 1.00
## trait_typebody_size:warm_coldcold:assay_temp_diff -0.00 0.01 1.00
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.07 -0.05 1.00
## trait_typesurvival:warm_coldcold:assay_temp_diff -0.05 -0.04 1.00
## trait_typebody_size:warm_coldwarm:assay_temp_diff -0.00 0.00 1.00
## trait_typefecundity:warm_coldwarm:assay_temp_diff -0.00 0.01 1.00
## trait_typesurvival:warm_coldwarm:assay_temp_diff -0.00 0.01 1.00
## trait_typebody_size:warm_coldcold:scaleselect_temp_diff -0.03 0.04 1.00
## trait_typefecundity:warm_coldcold:scaleselect_temp_diff -0.19 0.12 1.00
## trait_typesurvival:warm_coldcold:scaleselect_temp_diff -1.89 1.27 1.00
## trait_typebody_size:warm_coldwarm:scaleselect_temp_diff -0.03 0.01 1.00
## trait_typefecundity:warm_coldwarm:scaleselect_temp_diff -0.05 0.06 1.00
## trait_typesurvival:warm_coldwarm:scaleselect_temp_diff -0.17 0.19 1.00
## trait_typebody_size:warm_coldcold:scalegen_selection -0.02 0.02 1.00
## trait_typefecundity:warm_coldcold:scalegen_selection -0.29 -0.01 1.00
## trait_typesurvival:warm_coldcold:scalegen_selection -0.48 1.17 1.00
## trait_typebody_size:warm_coldwarm:scalegen_selection -0.01 0.01 1.00
## trait_typefecundity:warm_coldwarm:scalegen_selection -0.12 0.09 1.00
## trait_typesurvival:warm_coldwarm:scalegen_selection -0.70 0.27 1.00
## trait_typebody_size:warm_coldcold:scalegen_common_garden -0.17 0.07 1.00
## trait_typefecundity:warm_coldcold:scalegen_common_garden -0.47 0.69 1.00
## trait_typesurvival:warm_coldcold:scalegen_common_garden 0.69 5.46 1.00
## trait_typebody_size:warm_coldwarm:scalegen_common_garden -0.06 0.13 1.00
## trait_typefecundity:warm_coldwarm:scalegen_common_garden -0.01 0.05 1.00
## trait_typesurvival:warm_coldwarm:scalegen_common_garden 0.98 3.71 1.00
## Bulk_ESS Tail_ESS
## trait_typebody_size:warm_coldcold 2445 2573
## trait_typefecundity:warm_coldcold 4443 5130
## trait_typesurvival:warm_coldcold 3462 3694
## trait_typebody_size:warm_coldwarm 2788 2776
## trait_typefecundity:warm_coldwarm 3540 4041
## trait_typesurvival:warm_coldwarm 3347 3847
## trait_typebody_size:warm_coldcold:assay_temp_diff 2230 3243
## trait_typefecundity:warm_coldcold:assay_temp_diff 4978 5833
## trait_typesurvival:warm_coldcold:assay_temp_diff 3983 5028
## trait_typebody_size:warm_coldwarm:assay_temp_diff 3888 5504
## trait_typefecundity:warm_coldwarm:assay_temp_diff 3743 5686
## trait_typesurvival:warm_coldwarm:assay_temp_diff 6812 5865
## trait_typebody_size:warm_coldcold:scaleselect_temp_diff 1988 3222
## trait_typefecundity:warm_coldcold:scaleselect_temp_diff 5040 4919
## trait_typesurvival:warm_coldcold:scaleselect_temp_diff 2707 3185
## trait_typebody_size:warm_coldwarm:scaleselect_temp_diff 3932 4928
## trait_typefecundity:warm_coldwarm:scaleselect_temp_diff 5447 5614
## trait_typesurvival:warm_coldwarm:scaleselect_temp_diff 5039 5053
## trait_typebody_size:warm_coldcold:scalegen_selection 2539 3874
## trait_typefecundity:warm_coldcold:scalegen_selection 4291 4598
## trait_typesurvival:warm_coldcold:scalegen_selection 2585 3364
## trait_typebody_size:warm_coldwarm:scalegen_selection 4828 5062
## trait_typefecundity:warm_coldwarm:scalegen_selection 4797 4678
## trait_typesurvival:warm_coldwarm:scalegen_selection 4011 4623
## trait_typebody_size:warm_coldcold:scalegen_common_garden 3429 4295
## trait_typefecundity:warm_coldcold:scalegen_common_garden 5690 5471
## trait_typesurvival:warm_coldcold:scalegen_common_garden 1730 2239
## trait_typebody_size:warm_coldwarm:scalegen_common_garden 3496 4478
## trait_typefecundity:warm_coldwarm:scalegen_common_garden 6349 5572
## trait_typesurvival:warm_coldwarm:scalegen_common_garden 1677 1288
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnRR_model_all_moderators, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(
# Simplify `trait_type` and `warm_cold` interactions
Parameter = gsub("trait_type([a-z_]+):warm_cold(cold|warm)", "\\1(\\2)", Parameter),
# Adjust for assay_temp_diff
Parameter = gsub("trait_type([a-z_]+)\\((cold|warm)\\):assay_temp_diff", "\\1(\\2): assay_temp_diff", Parameter),
# Remove "scale" from all moderator variables
Parameter = gsub("scale", "", Parameter)
) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~ round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size(cold) | 0.015 | -0.035 | 0.064 |
| fecundity(cold) | -0.045 | -0.235 | 0.150 |
| survival(cold) | -0.071 | -0.662 | 0.508 |
| body_size(warm) | -0.006 | -0.055 | 0.038 |
| fecundity(warm) | -0.042 | -0.142 | 0.060 |
| survival(warm) | 0.054 | -0.307 | 0.431 |
| body_size(cold):assay_temp_diff | 0.002 | -0.003 | 0.008 |
| fecundity(cold):assay_temp_diff | -0.063 | -0.073 | -0.052 |
| survival(cold):assay_temp_diff | -0.045 | -0.055 | -0.035 |
| body_size(warm):assay_temp_diff | 0.001 | -0.003 | 0.005 |
| fecundity(warm):assay_temp_diff | 0.004 | -0.003 | 0.012 |
| survival(warm):assay_temp_diff | 0.002 | -0.005 | 0.009 |
| body_size(cold):select_temp_diff | 0.005 | -0.027 | 0.039 |
| fecundity(cold):select_temp_diff | -0.032 | -0.190 | 0.120 |
| survival(cold):select_temp_diff | -0.289 | -1.893 | 1.267 |
| body_size(warm):select_temp_diff | -0.008 | -0.028 | 0.012 |
| fecundity(warm):select_temp_diff | 0.006 | -0.050 | 0.064 |
| survival(warm):select_temp_diff | 0.012 | -0.174 | 0.194 |
| body_size(cold):gen_selection | 0.001 | -0.021 | 0.021 |
| fecundity(cold):gen_selection | -0.144 | -0.287 | -0.009 |
| survival(cold):gen_selection | 0.300 | -0.480 | 1.174 |
| body_size(warm):gen_selection | 0.000 | -0.010 | 0.011 |
| fecundity(warm):gen_selection | -0.018 | -0.123 | 0.086 |
| survival(warm):gen_selection | -0.217 | -0.700 | 0.274 |
| body_size(cold):gen_common_garden | -0.052 | -0.169 | 0.071 |
| fecundity(cold):gen_common_garden | 0.110 | -0.468 | 0.690 |
| survival(cold):gen_common_garden | 2.891 | 0.690 | 5.460 |
| body_size(warm):gen_common_garden | 0.034 | -0.058 | 0.132 |
| fecundity(warm):gen_common_garden | 0.019 | -0.012 | 0.049 |
| survival(warm):gen_common_garden | 2.564 | 0.983 | 3.713 |
# Generate predicted values at different assay temperatures
emmeans_full_model <- as.data.frame(emmeans(
lnRR_model_all_moderators,
specs = ~ assay_temp_diff * select_temp_diff * gen_selection * gen_common_garden | trait_type * warm_cold,
at = list(assay_temp_diff = c(-8.5, -4, 0, 4, 5, -5, 17, 20)) # Predict at unique min, control, max values
))
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_assay = min(assay_temp_diff),
max_assay = max(assay_temp_diff),
control_assay = 0
)
emmeans_full_model$trait_type <- factor(emmeans_full_model$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_full_model$warm_cold <- factor(emmeans_full_model$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_full_model %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
assay_temp_diff == min_assay |
assay_temp_diff == max_assay |
assay_temp_diff == control_assay) %>%
dplyr::select(-min_assay, -max_assay, -control_assay) %>%
mutate(percentage_change = (exp(emmean) - 1) * 100,
percentage_lower_HPD = (exp(lower.HPD) - 1) * 100,
percentage_upper_HPD = (exp(upper.HPD) - 1) * 100) %>%
mutate(across(c(emmean,
lower.HPD,
upper.HPD,
percentage_change,
percentage_lower_HPD,
percentage_upper_HPD), ~ round(., 3))) %>% # Round numbers to 3 decimal points
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| assay_temp_diff | select_temp_diff | gen_selection | gen_common_garden | trait_type | warm_cold | emmean | lower.HPD | upper.HPD | percentage_change | percentage_lower_HPD | percentage_upper_HPD |
|---|---|---|---|---|---|---|---|---|---|---|---|
| -8.5 | 5.64645 | 29.0084 | 2.710084 | Body size | Cold | -0.002 | -0.068 | 0.063 | -0.174 | -6.570 | 6.513 |
| 0.0 | 5.64645 | 29.0084 | 2.710084 | Body size | Cold | 0.015 | -0.033 | 0.066 | 1.532 | -3.216 | 6.847 |
| 5.0 | 5.64645 | 29.0084 | 2.710084 | Body size | Cold | 0.026 | -0.028 | 0.083 | 2.583 | -2.791 | 8.605 |
| -8.5 | 5.64645 | 29.0084 | 2.710084 | Fecundity | Cold | 0.492 | 0.287 | 0.693 | 63.516 | 33.287 | 100.029 |
| 0.0 | 5.64645 | 29.0084 | 2.710084 | Fecundity | Cold | -0.045 | -0.235 | 0.150 | -4.374 | -20.917 | 16.194 |
| 5.0 | 5.64645 | 29.0084 | 2.710084 | Fecundity | Cold | -0.360 | -0.565 | -0.155 | -30.208 | -43.189 | -14.385 |
| -8.5 | 5.64645 | 29.0084 | 2.710084 | Survival | Cold | 0.315 | -0.258 | 0.919 | 36.989 | -22.732 | 150.565 |
| 0.0 | 5.64645 | 29.0084 | 2.710084 | Survival | Cold | -0.069 | -0.679 | 0.491 | -6.711 | -49.275 | 63.319 |
| 4.0 | 5.64645 | 29.0084 | 2.710084 | Survival | Cold | -0.250 | -0.870 | 0.298 | -22.093 | -58.093 | 34.776 |
| 0.0 | 5.64645 | 29.0084 | 2.710084 | Body size | Warm | -0.006 | -0.053 | 0.039 | -0.591 | -5.122 | 4.027 |
| -5.0 | 5.64645 | 29.0084 | 2.710084 | Body size | Warm | -0.010 | -0.062 | 0.047 | -1.006 | -5.979 | 4.817 |
| 17.0 | 5.64645 | 29.0084 | 2.710084 | Body size | Warm | 0.007 | -0.063 | 0.077 | 0.753 | -6.076 | 8.004 |
| -4.0 | 5.64645 | 29.0084 | 2.710084 | Fecundity | Warm | -0.059 | -0.170 | 0.052 | -5.715 | -15.648 | 5.345 |
| 0.0 | 5.64645 | 29.0084 | 2.710084 | Fecundity | Warm | -0.043 | -0.145 | 0.056 | -4.167 | -13.472 | 5.761 |
| 20.0 | 5.64645 | 29.0084 | 2.710084 | Fecundity | Warm | 0.039 | -0.119 | 0.197 | 4.023 | -11.244 | 21.776 |
| 0.0 | 5.64645 | 29.0084 | 2.710084 | Survival | Warm | 0.050 | -0.317 | 0.416 | 5.092 | -27.163 | 51.561 |
| -5.0 | 5.64645 | 29.0084 | 2.710084 | Survival | Warm | 0.038 | -0.330 | 0.409 | 3.924 | -28.111 | 50.568 |
| 17.0 | 5.64645 | 29.0084 | 2.710084 | Survival | Warm | 0.089 | -0.302 | 0.443 | 9.325 | -26.032 | 55.763 |
Data visualisation
Marginal means
# Generate predictions at assay_temp_diff = 0, or 5 degrees below or above the control temperatures
emmeans_full_model <- as.data.frame(emmeans(
lnRR_model_all_moderators,
specs = ~ assay_temp_diff * select_temp_diff * gen_selection * gen_common_garden | trait_type * warm_cold,
at = list(assay_temp_diff = c(-5, 0, 5) # Re-acclimated to control, or 5 degrees above/below the control
)))
# Rename variable levels
emmeans_full_model$trait_type <- factor(emmeans_full_model$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_full_model$warm_cold <- factor(emmeans_full_model$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
emmeans_full_model
## assay_temp_diff select_temp_diff gen_selection gen_common_garden trait_type
## 1 -5 5.64645 29.0084 2.710084 Body size
## 2 0 5.64645 29.0084 2.710084 Body size
## 3 5 5.64645 29.0084 2.710084 Body size
## 4 -5 5.64645 29.0084 2.710084 Fecundity
## 5 0 5.64645 29.0084 2.710084 Fecundity
## 6 5 5.64645 29.0084 2.710084 Fecundity
## 7 -5 5.64645 29.0084 2.710084 Survival
## 8 0 5.64645 29.0084 2.710084 Survival
## 9 5 5.64645 29.0084 2.710084 Survival
## 10 -5 5.64645 29.0084 2.710084 Body size
## 11 0 5.64645 29.0084 2.710084 Body size
## 12 5 5.64645 29.0084 2.710084 Body size
## 13 -5 5.64645 29.0084 2.710084 Fecundity
## 14 0 5.64645 29.0084 2.710084 Fecundity
## 15 5 5.64645 29.0084 2.710084 Fecundity
## 16 -5 5.64645 29.0084 2.710084 Survival
## 17 0 5.64645 29.0084 2.710084 Survival
## 18 5 5.64645 29.0084 2.710084 Survival
## warm_cold emmean lower.HPD upper.HPD
## 1 Cold 0.005148823 -0.04970621 0.05952856
## 2 Cold 0.015207363 -0.03269314 0.06623147
## 3 Cold 0.025500722 -0.02830826 0.08255127
## 4 Cold 0.270851221 0.06979423 0.46014868
## 5 Cold -0.044725107 -0.23467509 0.15009005
## 6 Cold -0.359657441 -0.56543988 -0.15530694
## 7 Cold 0.156280791 -0.42491192 0.74822331
## 8 Cold -0.069472920 -0.67875367 0.49053716
## 9 Cold -0.295030787 -0.91381113 0.25757028
## 10 Warm -0.010110065 -0.06165284 0.04704290
## 11 Warm -0.005926964 -0.05257416 0.03948043
## 12 Warm -0.001969011 -0.04986516 0.03975194
## 13 Warm -0.063105837 -0.17796598 0.05170522
## 14 Warm -0.042562540 -0.14470383 0.05601262
## 15 Warm -0.022118434 -0.11624918 0.08314638
## 16 Warm 0.038493731 -0.33004718 0.40924350
## 17 Warm 0.049664228 -0.31694843 0.41581910
## 18 Warm 0.061498001 -0.30913205 0.42179338
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75,
lwd=1) +
geom_quasirandom(data = data, # Plot effect sizes, scaled by precision
aes(x = trait_type:warm_cold,
y = lnRR,
fill = trait_type:warm_cold,
size = 1/sqrt(var_lnRR)),
color = "black",
shape = 21,
width = 0.3,
alpha = 0.3) +
geom_errorbar(data = filter(emmeans_full_model,
assay_temp_diff == "-5"), # 5 degrees below control (background)
aes(x = trait_type:warm_cold,
ymin = lower.HPD,
ymax = upper.HPD),
color="white",
size = 2.75,
width = 0.16,
position = position_nudge(x=-0.25)) +
geom_point(data = filter(emmeans_full_model,
assay_temp_diff == "-5"), # 5 degrees below control (background)
aes(x = trait_type:warm_cold,
y = emmean),
shape = 21,
size = 4.5,
stroke = 2,
color="white",
fill = "white",
position = position_nudge(x=-0.25)) +
geom_errorbar(data = filter(emmeans_full_model,
assay_temp_diff == "-5"), # 5 degrees below control (credible intervals)
aes(x = trait_type:warm_cold,
ymin = lower.HPD,
ymax = upper.HPD),
color="#06B4BA",
size = 2,
width = 0.12,
position = position_nudge(x=-0.25)) +
geom_point(data = filter(emmeans_full_model,
assay_temp_diff == "-5"), # 5 degrees below control (point estimates)
aes(x = trait_type:warm_cold,
y = emmean),
shape = 21,
size = 3.5,
stroke = 2,
color="#06B4BA",
fill = "white",
position = position_nudge(x=-0.25)) +
geom_errorbar(data = filter(emmeans_full_model,
assay_temp_diff == "0"), # control temperature (background)
aes(x = trait_type:warm_cold,
ymin = lower.HPD,
ymax = upper.HPD),
color="white",
size = 2.75,
width = 0.16,
position = position_nudge(x=0)) +
geom_point(data = filter(emmeans_full_model,
assay_temp_diff == "0"), # control temperature (background)
aes(x = trait_type:warm_cold,
y = emmean),
shape = 22,
size = 4.5,
stroke = 2,
color="white",
fill = "white",
position = position_nudge(x=0)) +
geom_errorbar(data = filter(emmeans_full_model,
assay_temp_diff == "0"), # control temperature (credible intervals)
aes(x = trait_type:warm_cold,
ymin = lower.HPD,
ymax = upper.HPD),
color="black",
size = 2,
width = 0.12,
position = position_nudge(x=0)) +
geom_point(data = filter(emmeans_full_model,
assay_temp_diff == "0"), # control temperature (point estimates)
aes(x = trait_type:warm_cold,
y = emmean),
shape = 22,
size = 3.5,
stroke = 2,
color="black",
fill = "white",
position = position_nudge(x=0)) +
geom_errorbar(data = filter(emmeans_full_model,
assay_temp_diff == "5"), # 5 degrees above control (background)
aes(x = trait_type:warm_cold,
ymin = lower.HPD,
ymax = upper.HPD),
color="white",
size = 2.75,
width = 0.16,
position = position_nudge(x=0.25)) +
geom_point(data = filter(emmeans_full_model,
assay_temp_diff == "5"), # 5 degrees above control (background)
aes(x = trait_type:warm_cold,
y = emmean),
shape = 23,
size = 4.5,
stroke = 2,
color="white",
fill = "white",
position = position_nudge(x=0.25)) +
geom_errorbar(data = filter(emmeans_full_model,
assay_temp_diff == "5"), # 5 degrees above control (credible intervals)
aes(x = trait_type:warm_cold,
ymin = lower.HPD,
ymax = upper.HPD),
color="#E80756",
size = 2,
width = 0.12,
position = position_nudge(x=0.25)) +
geom_point(data = filter(emmeans_full_model,
assay_temp_diff == "5"), # 5 degrees above control (point estimates)
aes(x = trait_type:warm_cold,
y = emmean),
shape = 23,
size = 3.5,
stroke = 2,
color="#E80756",
fill = "white",
position = position_nudge(x=0.25)) +
geom_text(data = sample_sizes, # Sample size annotations
aes(x = trait_type:warm_cold,
y = 1.5,
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1, size = 5, show.legend = FALSE) +
theme_bw() + # Customize the plot
labs(y = "lnRR", x = "") +
scale_size_continuous(range = c(2, 8))+
scale_fill_manual(values = c("#06B4BA", "#E80756", "#06B4BA", "#E80756", "#06B4BA", "#E80756"))+
scale_color_manual(values = c("Cold" = "#06B4BA", "Warm" = "#E80756")) + # Text colour
theme(text = element_text(size = 20, color = "black"),
legend.title = ggplot2::element_text(size = 16),
legend.text = ggplot2::element_text(size = 14),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5)) +
guides(fill = "none", size = "none")+
coord_flip() +
ylim(-1.5, 1.5)
ggsave(file = "fig/lnRR_all_moderators_marginal_means.png", width = 12, height = 8, dpi = 500)
Overall trend with assay temperature
# Generate predictions for all assay temperatures
emmeans_full_model_assay <- as.data.frame(emmeans(
lnRR_model_all_moderators,
specs = ~ assay_temp_diff * select_temp_diff * gen_selection * gen_common_garden | trait_type * warm_cold,
at = list(assay_temp_diff = seq(min(data$assay_temp_diff), max(data$assay_temp_diff), by = 0.5) # Re-acclimated to control, or 5 degrees above/below the contol
)))
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_assay_temp_diff = min(assay_temp_diff),
max_assay_temp_diff = max(assay_temp_diff)
)
# Rename variable levels
emmeans_full_model_assay$trait_type <- factor(emmeans_full_model_assay$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_full_model_assay$warm_cold <- factor(emmeans_full_model_assay$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_full_model_assay <- emmeans_full_model_assay %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
assay_temp_diff >= min_assay_temp_diff,
assay_temp_diff <= max_assay_temp_diff
)
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_vline(xintercept = 0, # Vertical line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = assay_temp_diff,
y = lnRR,
size = 1/sqrt(var_lnRR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_full_model_assay, # Shaded area for credible intervals
aes(x = assay_temp_diff,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_full_model_assay, # Predicted regression line
aes(y = emmean,
x = assay_temp_diff,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -2, -2.75), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Assay temperature difference", y = "lnRR",
col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 25, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 22)) +
guides(fill = "none", size = "none")+
coord_cartesian(ylim = c(-3, 3))
ggsave(file = "fig/lnRR_all_moderators_assay_temp_diff.png", width = 12, height = 10, dpi = 500)
Changes in relative trait variance (lnCVR)
Overall model
Model specification
# Model specification
formula <- bf(lnCVR ~ trait_type -1 + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnCVR)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnCVR_model_overall <- brm(formula,
family = gaussian(),
data = data,
data2 = list(phylo_matrix = phylo_matrix,
VCV_lnCVR = VCV_lnCVR),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnCVR_model_overall, file = "RData/lnCVR_model_overall.rds")
Model output
# Load model
lnCVR_model_overall <- readRDS("RData/lnCVR_model_overall.rds")
# Display model output
summary(lnCVR_model_overall)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnCVR ~ trait_type - 1 + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnCVR)
## Data: data (Number of observations: 476)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 88)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.11 0.05 0.01 0.22 1.00 1362 2302
##
## ~obs (Number of levels: 476)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.40 0.04 0.32 0.48 1.00 2165 4001
##
## ~phylogeny (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.10 0.08 0.00 0.29 1.00 3123 3747
##
## ~ref (Number of levels: 44)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.06 0.05 0.00
## sd(trait_typefecundity) 0.14 0.09 0.01
## sd(trait_typesurvival) 0.48 0.15 0.21
## cor(trait_typebody_size,trait_typefecundity) 0.09 0.49 -0.84
## cor(trait_typebody_size,trait_typesurvival) -0.03 0.49 -0.88
## cor(trait_typefecundity,trait_typesurvival) -0.06 0.43 -0.85
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.18 1.00 4025 4524
## sd(trait_typefecundity) 0.34 1.00 1577 3479
## sd(trait_typesurvival) 0.81 1.00 3029 3706
## cor(trait_typebody_size,trait_typefecundity) 0.89 1.00 2800 4724
## cor(trait_typebody_size,trait_typesurvival) 0.87 1.00 1260 3139
## cor(trait_typefecundity,trait_typesurvival) 0.76 1.00 2096 3841
##
## ~species (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.07 0.05 0.00 0.20 1.00 3402 4336
##
## Regression Coefficients:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## trait_typebody_size 0.04 0.10 -0.14 0.25 1.00 3920 4162
## trait_typefecundity -0.01 0.10 -0.21 0.19 1.00 4316 4214
## trait_typesurvival 0.13 0.16 -0.19 0.45 1.00 4845 5467
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnCVR_model_overall, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(Parameter = gsub("trait_type([a-z_]+)", "\\1", Parameter)) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size | 0.045 | -0.139 | 0.250 |
| fecundity | -0.013 | -0.212 | 0.192 |
| survival | 0.127 | -0.185 | 0.449 |
Heterogeneity analysis
# Extracting the posterior distribution from the model
posterior <- posterior_samples(lnCVR_model_overall)
# Calculate measurement error variance
sigma2_v <- sum(1/diag(VCV_lnCVR)) * (length(diag(VCV_lnCVR))- 1)/(sum(1/diag(VCV_lnCVR))^2 - sum((1/diag(VCV_lnCVR))^2))
# Calculate the total variance (including the measurement error variance)
var_total <- posterior$sd_experiment_ID__Intercept +
posterior$sd_obs__Intercept +
posterior$sd_phylogeny__Intercept +
posterior$sd_ref__trait_typebody_size +
posterior$sd_ref__trait_typefecundity +
posterior$sd_ref__trait_typesurvival +
posterior$sd_species__Intercept +
sigma2_v
# Calculate heterogeneity (I2)
I2 <- list(
I2_total = (var_total - sigma2_v) / var_total, # Total heterogeneity
I2_study = (posterior$sd_ref__trait_typebody_size + # Heterogeneity explained by study differences
posterior$sd_ref__trait_typefecundity +
posterior$sd_ref__trait_typesurvival) / var_total,
I2_exp = posterior$sd_experiment_ID__Intercept / var_total, # Heterogeneity explained by experiment differences
I2_sp = posterior$sd_species__Intercept / var_total, # Heterogeneity explained by species differences
I2_phylo = posterior$sd_phylogeny__Intercept / var_total, # Heterogeneity explained by phylogenetic relatedness
I2_obs = posterior$sd_obs__Intercept / var_total # Heterogeneity explained by residual variation
)
# Calculate mean and credible intervals
summary_table <- t(sapply(I2, function(x) {
c(Mean = 100*round(mean(x),4),
` ` = 100* round(quantile(x, 0.025),4),
` ` = 100*round(quantile(x, 0.975),4))
}))
# Customise table
summary_table <- summary_table %>%
as.data.frame() %>%
rownames_to_column("Variable") %>%
mutate(
Variable = recode(Variable,
"I2_total" = "Total",
"I2_study" = "Study",
"I2_exp" = "Experiment",
"I2_sp" = "Species",
"I2_phylo" = "Phylogeny",
"I2_obs" = "Residual")
) %>%
rename(
lower_HPD = ` .2.5%`,
upper_HPD = ` .97.5%`
) %>%
mutate(
Variable = paste0("I<sup>2</sup><sub>", Variable) # Create a new column with formatted I² and subscripts
)
# Display table
kable(summary_table, "html", escape = FALSE,
col.names = c("Heterogeneity (%)", "Mean", "Lower HPD", "Upper HPD"),
align = c('l', 'r', 'r', 'r')) %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, width = "200px", bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Heterogeneity (%) | Mean | Lower HPD | Upper HPD |
|---|---|---|---|
| I2Total | 90.51 | 87.57 | 92.92 |
| I2Study | 44.97 | 29.15 | 59.30 |
| I2Experiment | 7.61 | 0.72 | 14.61 |
| I2Species | 4.40 | 0.16 | 12.65 |
| I2Phylogeny | 6.28 | 0.30 | 17.32 |
| I2Residual | 27.26 | 18.94 | 37.61 |
Data visualisation
# Generate predictions
emmeans_overall <- as.data.frame(emmeans(lnCVR_model_overall, ~ trait_type))
emmeans_overall$trait_type <- factor(emmeans_overall$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75,
lwd=1) +
geom_quasirandom(data = data, # Plot effect sizes, scaled by precision
aes(x = trait_type,
y = lnCVR,
size = 1/sqrt(var_lnCVR)),
fill = "gray75",
shape = 21,
width = 0.25,
alpha = 0.2) +
geom_errorbar(data = emmeans_overall, # Plot the point estimates in white for background
aes(ymin = lower.HPD,
ymax = upper.HPD,
x = trait_type),
size = 2,
width = 0.085,
color = "white") +
geom_point(data = emmeans_overall, # Plot the credible intervals in white for background
aes(x = trait_type,
y = emmean),
size = 4.5,
stroke = 1.5,
shape = 21,
color="white",
fill = "white") +
geom_errorbar(data = emmeans_overall, # Plot the point estimates
aes(x = trait_type,
ymin = lower.HPD,
ymax = upper.HPD),
size = 1.5,
width = 0.075,
color = "black") +
geom_point(data = emmeans_overall, # Plot the credible intervals
aes(x = trait_type,
y = emmean),
size = 4,
shape = 21,
stroke = 1.5,
color="black",
fill = "white") +
geom_text(data = sample_sizes_traits, # Sample size annotations
aes(x = trait_type,
y = 3,
label = paste0("k = ", estimates, " (", studies, ")")),
hjust = 1, size = 5, show.legend = FALSE) +
theme_bw() + # Customize the plot
labs(y = "lnCVR", x = "") +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 20, color = "black"),
legend.title = ggplot2::element_text(size = 16),
legend.text = ggplot2::element_text(size = 14),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5)) +
guides(color = "none", size = "none")+
coord_flip() +
ylim(-3, 3)
# Save figure
ggsave(file = "fig/lnCVR_overall.png", width = 12, height = 7, dpi = 500)
Temperature regime
Model specification
# Model specification
formula <- bf(lnCVR ~ trait_type:warm_cold -1 + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnCVR)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnCVR_model_warm_cold <- brm(formula,
family = gaussian(),
data = data,
data2 = list(phylo_matrix = phylo_matrix,
VCV_lnCVR = VCV_lnCVR),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnCVR_model_warm_cold, file = "RData/lnCVR_model_warm_cold.rds")
Model output
# Load model
lnCVR_model_warm_cold <- readRDS("RData/lnCVR_model_warm_cold.rds")
# Display model output
summary(lnCVR_model_warm_cold)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnCVR ~ trait_type:warm_cold - 1 + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnCVR)
## Data: data (Number of observations: 476)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 88)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.11 0.05 0.01 0.21 1.00 1334 1916
##
## ~obs (Number of levels: 476)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.40 0.04 0.32 0.49 1.00 2074 4187
##
## ~phylogeny (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.09 0.07 0.00 0.28 1.00 3146 4236
##
## ~ref (Number of levels: 44)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.06 0.05 0.00
## sd(trait_typefecundity) 0.13 0.09 0.01
## sd(trait_typesurvival) 0.49 0.15 0.22
## cor(trait_typebody_size,trait_typefecundity) 0.07 0.49 -0.86
## cor(trait_typebody_size,trait_typesurvival) -0.00 0.51 -0.89
## cor(trait_typefecundity,trait_typesurvival) -0.04 0.43 -0.84
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.17 1.00 3651 3866
## sd(trait_typefecundity) 0.33 1.00 1485 3152
## sd(trait_typesurvival) 0.81 1.00 3533 4364
## cor(trait_typebody_size,trait_typefecundity) 0.89 1.00 3524 4596
## cor(trait_typebody_size,trait_typesurvival) 0.89 1.00 1148 2616
## cor(trait_typefecundity,trait_typesurvival) 0.79 1.00 2256 3614
##
## ~species (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.06 0.05 0.00 0.19 1.00 4014 5084
##
## Regression Coefficients:
## Estimate Est.Error l-95% CI u-95% CI Rhat
## trait_typebody_size:warm_coldcold -0.02 0.12 -0.25 0.22 1.00
## trait_typefecundity:warm_coldcold -0.15 0.20 -0.53 0.23 1.00
## trait_typesurvival:warm_coldcold 0.00 0.27 -0.52 0.53 1.00
## trait_typebody_size:warm_coldwarm 0.06 0.10 -0.13 0.26 1.00
## trait_typefecundity:warm_coldwarm -0.00 0.10 -0.19 0.20 1.00
## trait_typesurvival:warm_coldwarm 0.14 0.17 -0.19 0.47 1.00
## Bulk_ESS Tail_ESS
## trait_typebody_size:warm_coldcold 3244 3195
## trait_typefecundity:warm_coldcold 4902 4558
## trait_typesurvival:warm_coldcold 5701 5908
## trait_typebody_size:warm_coldwarm 3466 3174
## trait_typefecundity:warm_coldwarm 3853 2660
## trait_typesurvival:warm_coldwarm 5207 4433
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnCVR_model_warm_cold, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(
# Simplify `trait_type` and `warm_cold` interactions
Parameter = gsub("trait_type([a-z_]+):warm_cold(cold|warm)", "\\1(\\2)", Parameter)
) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~ round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size(cold) | -0.020 | -0.251 | 0.216 |
| fecundity(cold) | -0.152 | -0.529 | 0.227 |
| survival(cold) | 0.001 | -0.522 | 0.533 |
| body_size(warm) | 0.057 | -0.132 | 0.259 |
| fecundity(warm) | -0.002 | -0.194 | 0.201 |
| survival(warm) | 0.141 | -0.190 | 0.471 |
Contrasts
# Generate predictions
emms <- emmeans(lnCVR_model_warm_cold, specs = ~warm_cold | trait_type)
# Generate contrasts
contrast(emms, method = "pairwise")
## trait_type = body_size:
## contrast estimate lower.HPD upper.HPD
## cold - warm -0.0779 -0.265 0.114
##
## trait_type = fecundity:
## contrast estimate lower.HPD upper.HPD
## cold - warm -0.1509 -0.516 0.228
##
## trait_type = survival:
## contrast estimate lower.HPD upper.HPD
## cold - warm -0.1437 -0.667 0.384
##
## Point estimate displayed: median
## HPD interval probability: 0.95
Data visualisation
# Generate predictions
emmeans_warm_cold <- as.data.frame(emmeans(lnCVR_model_warm_cold,
specs = ~ warm_cold | trait_type))
emmeans_warm_cold$trait_type <- factor(emmeans_warm_cold$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_warm_cold$warm_cold <- factor(emmeans_warm_cold$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75,
lwd=1) +
geom_quasirandom(data = data, # Plot effect sizes, scaled by precision
aes(x = trait_type:warm_cold,
y = lnCVR,
fill = trait_type:warm_cold,
size = 1/sqrt(var_lnCVR)),
color = "black",
shape = 21,
width = 0.25,
alpha = 0.3) +
geom_errorbar(data = emmeans_warm_cold, # Plot the point estimates in white for background
aes(x = trait_type:warm_cold,
ymin = lower.HPD,
ymax = upper.HPD),
color="white",
size = 2.5,
width = 0.17) +
geom_point(data = emmeans_warm_cold, # Plot the credible intervals in white for background
aes(x = trait_type:warm_cold,
y = emmean),
shape = 21,
size = 4,
stroke = 2,
color="white",
fill = "white") +
geom_errorbar(data = emmeans_warm_cold, # Plot the point estimates
aes(x = trait_type:warm_cold,
ymin = lower.HPD,
ymax = upper.HPD),
color="black",
size = 2,
width = 0.15) +
geom_point(data = emmeans_warm_cold, # Plot the credible intervals
aes(x = trait_type:warm_cold,
y = emmean),
shape = 21,
size = 3.5,
stroke = 2,
color="black",
fill = "white") +
geom_text(data = sample_sizes, # Sample size annotations
aes(x = trait_type:warm_cold,
y = 3,
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1, size = 5, show.legend = FALSE) +
theme_bw() + # Customize the plot
labs(y = "lnCVR", x = "") +
scale_size_continuous(range = c(2, 8))+
# scale_fill_manual(values = c("#A5F820", "#73B706", "#9CEFFC", "#06A2BA", "#FED2ED", "#B90674"))+
scale_fill_manual(values = c("#06B4BA", "#E80756", "#06B4BA", "#E80756", "#06B4BA", "#E80756"))+
theme(text = element_text(size = 20, color = "black"),
legend.title = ggplot2::element_text(size = 16),
legend.text = ggplot2::element_text(size = 14),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5)) +
guides(fill = "none", size = "none")+
coord_flip() +
ylim(-3, 3)
ggsave(file = "fig/lnCVR_warm_cold.png", width = 12, height = 7, dpi = 500)
Assay temperature difference
We calculated the difference between the assay temperature and control temperature (assay_temp_diff) to account for the fact that animals were tested to a different range of temperatures. We also predicted that effects will vary if traits are assayed at colder- or warmer-than-control conditions, depending on the temperature regime. Because the variation in assay temperature generates some nuisance heterogeneity (sensu Noble et al., 2017), the results of subsequent models were provided after controlling for assay temperature differences. In order words, the results of all moderators are conditional on assay temperature, meaning that results are interpretable as the difference between control and selected lines, when animals are tested at the control temperature.
Model specification
# Model specification
formula <- bf(lnCVR ~ 0 + trait_type:warm_cold + trait_type:warm_cold:assay_temp_diff + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnCVR)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnCVR_model_assay_temp_diff <- brm(formula,
family = gaussian(),
data = data,
data2 = list(phylo_matrix = phylo_matrix,
VCV_lnCVR = VCV_lnCVR),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnCVR_model_assay_temp_diff, file = "RData/lnCVR_model_assay_temp_diff.rds")
Model output
# Load model
lnCVR_model_assay_temp_diff <- readRDS("RData/lnCVR_model_assay_temp_diff.rds")
# Display model output
summary(lnCVR_model_assay_temp_diff)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnCVR ~ 0 + trait_type:warm_cold + trait_type:warm_cold:assay_temp_diff + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnCVR)
## Data: data (Number of observations: 476)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 88)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.09 0.05 0.01 0.20 1.01 1355 3293
##
## ~obs (Number of levels: 476)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.40 0.04 0.32 0.48 1.00 2181 4146
##
## ~phylogeny (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.09 0.07 0.00 0.28 1.00 3565 4130
##
## ~ref (Number of levels: 44)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.06 0.05 0.00
## sd(trait_typefecundity) 0.10 0.08 0.00
## sd(trait_typesurvival) 0.52 0.15 0.25
## cor(trait_typebody_size,trait_typefecundity) 0.05 0.49 -0.86
## cor(trait_typebody_size,trait_typesurvival) -0.00 0.50 -0.88
## cor(trait_typefecundity,trait_typesurvival) -0.05 0.45 -0.85
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.18 1.00 4100 5188
## sd(trait_typefecundity) 0.30 1.00 2451 3961
## sd(trait_typesurvival) 0.86 1.00 3645 3396
## cor(trait_typebody_size,trait_typefecundity) 0.88 1.00 5017 5682
## cor(trait_typebody_size,trait_typesurvival) 0.87 1.00 1598 3433
## cor(trait_typefecundity,trait_typesurvival) 0.81 1.00 2117 3653
##
## ~species (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.06 0.05 0.00 0.19 1.00 3679 4328
##
## Regression Coefficients:
## Estimate Est.Error l-95% CI
## trait_typebody_size:warm_coldcold -0.02 0.12 -0.24
## trait_typefecundity:warm_coldcold -0.28 0.20 -0.67
## trait_typesurvival:warm_coldcold 0.12 0.29 -0.45
## trait_typebody_size:warm_coldwarm 0.06 0.11 -0.14
## trait_typefecundity:warm_coldwarm -0.06 0.11 -0.26
## trait_typesurvival:warm_coldwarm 0.24 0.19 -0.13
## trait_typebody_size:warm_coldcold:assay_temp_diff -0.01 0.02 -0.05
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.14 0.04 -0.22
## trait_typesurvival:warm_coldcold:assay_temp_diff 0.03 0.04 -0.05
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.00 0.02 -0.03
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.02 0.02 -0.01
## trait_typesurvival:warm_coldwarm:assay_temp_diff -0.03 0.02 -0.07
## u-95% CI Rhat Bulk_ESS
## trait_typebody_size:warm_coldcold 0.23 1.00 4403
## trait_typefecundity:warm_coldcold 0.11 1.00 5896
## trait_typesurvival:warm_coldcold 0.69 1.00 7022
## trait_typebody_size:warm_coldwarm 0.28 1.00 4306
## trait_typefecundity:warm_coldwarm 0.16 1.00 5058
## trait_typesurvival:warm_coldwarm 0.62 1.00 5555
## trait_typebody_size:warm_coldcold:assay_temp_diff 0.03 1.00 4918
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.06 1.00 8871
## trait_typesurvival:warm_coldcold:assay_temp_diff 0.10 1.00 8902
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.04 1.00 6932
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.05 1.00 7381
## trait_typesurvival:warm_coldwarm:assay_temp_diff 0.02 1.00 7678
## Tail_ESS
## trait_typebody_size:warm_coldcold 4527
## trait_typefecundity:warm_coldcold 5950
## trait_typesurvival:warm_coldcold 6292
## trait_typebody_size:warm_coldwarm 4521
## trait_typefecundity:warm_coldwarm 4766
## trait_typesurvival:warm_coldwarm 5541
## trait_typebody_size:warm_coldcold:assay_temp_diff 5124
## trait_typefecundity:warm_coldcold:assay_temp_diff 6069
## trait_typesurvival:warm_coldcold:assay_temp_diff 6487
## trait_typebody_size:warm_coldwarm:assay_temp_diff 6990
## trait_typefecundity:warm_coldwarm:assay_temp_diff 6687
## trait_typesurvival:warm_coldwarm:assay_temp_diff 6171
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnCVR_model_assay_temp_diff, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(Parameter = gsub("trait_type([a-z_]+):warm_cold(cold|warm)", "\\1(\\2)",
Parameter), Parameter = gsub("trait_type([a-z_]+)\\(warm\\):assay_temp_diff",
"\\1(warm): assay_temp_diff", Parameter)) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size(cold) | -0.017 | -0.237 | 0.230 |
| fecundity(cold) | -0.281 | -0.667 | 0.112 |
| survival(cold) | 0.118 | -0.450 | 0.694 |
| body_size(warm) | 0.055 | -0.144 | 0.277 |
| fecundity(warm) | -0.055 | -0.259 | 0.158 |
| survival(warm) | 0.235 | -0.133 | 0.619 |
| body_size(cold):assay_temp_diff | -0.007 | -0.049 | 0.034 |
| fecundity(cold):assay_temp_diff | -0.139 | -0.216 | -0.063 |
| survival(cold):assay_temp_diff | 0.028 | -0.048 | 0.101 |
| body_size(warm):assay_temp_diff | 0.002 | -0.031 | 0.036 |
| fecundity(warm):assay_temp_diff | 0.018 | -0.013 | 0.048 |
| survival(warm):assay_temp_diff | -0.025 | -0.074 | 0.022 |
Data visualisation
# Generate predictions
emmeans_assay_temp_diff <- as.data.frame(emmeans(
lnCVR_model_assay_temp_diff,
specs = ~ assay_temp_diff | trait_type * warm_cold,
at = list(assay_temp_diff = seq(min(data$assay_temp_diff), max(data$assay_temp_diff), by = 0.5)))
)
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_assay_temp_diff = min(assay_temp_diff),
max_assay_temp_diff = max(assay_temp_diff)
)
emmeans_assay_temp_diff$trait_type <- factor(emmeans_assay_temp_diff$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_assay_temp_diff$warm_cold <- factor(emmeans_assay_temp_diff$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_assay_temp_diff <- emmeans_assay_temp_diff %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
assay_temp_diff >= min_assay_temp_diff,
assay_temp_diff <= max_assay_temp_diff
)
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_vline(xintercept = 0, # Vertical line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = assay_temp_diff,
y = lnCVR,
size = 1/sqrt(var_lnCVR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_assay_temp_diff, # Shaded area for credible intervals
aes(x = assay_temp_diff,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_assay_temp_diff, # Predicted regression line
aes(y = emmean,
x = assay_temp_diff,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -2, -2.75), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Assay temperature difference", y = "lnCVR",
col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 25, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 22)) +
guides(fill = "none", size = "none")+
coord_cartesian(ylim = c(-3, 3))
ggsave(file = "fig/lnCVR_assay_temp_diff.png", width = 12, height = 10, dpi = 500)
Selection temperature
Model specification
# Model specification
formula <- bf(lnCVR ~ 0 + trait_type:warm_cold +
trait_type:warm_cold:assay_temp_diff +
trait_type:warm_cold:select_temp_diff + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnCVR)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnCVR_model_sel_temp_diff <- brm(formula,
family = gaussian(),
data = data,
data2 = list(phylo_matrix = phylo_matrix,
VCV_lnCVR = VCV_lnCVR),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnCVR_model_sel_temp_diff, file = "RData/lnCVR_model_sel_temp_diff.rds")
Model output
# Load model
lnCVR_model_sel_temp_diff <- readRDS("RData/lnCVR_model_sel_temp_diff.rds")
# Display model output
summary(lnCVR_model_sel_temp_diff)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnCVR ~ 0 + trait_type:warm_cold + trait_type:warm_cold:assay_temp_diff + trait_type:warm_cold:select_temp_diff + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnCVR)
## Data: data (Number of observations: 476)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 88)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.10 0.06 0.01 0.21 1.00 1120 2464
##
## ~obs (Number of levels: 476)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.39 0.04 0.32 0.47 1.00 2157 3877
##
## ~phylogeny (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.09 0.08 0.00 0.28 1.00 3736 4900
##
## ~ref (Number of levels: 44)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.06 0.05 0.00
## sd(trait_typefecundity) 0.12 0.09 0.00
## sd(trait_typesurvival) 0.56 0.17 0.27
## cor(trait_typebody_size,trait_typefecundity) 0.01 0.50 -0.87
## cor(trait_typebody_size,trait_typesurvival) -0.01 0.49 -0.88
## cor(trait_typefecundity,trait_typesurvival) -0.01 0.44 -0.82
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.19 1.00 4189 4553
## sd(trait_typefecundity) 0.35 1.00 1509 3322
## sd(trait_typesurvival) 0.93 1.00 3008 3730
## cor(trait_typebody_size,trait_typefecundity) 0.89 1.00 3113 4820
## cor(trait_typebody_size,trait_typesurvival) 0.86 1.00 1144 2987
## cor(trait_typefecundity,trait_typesurvival) 0.82 1.01 1465 3650
##
## ~species (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.06 0.05 0.00 0.19 1.00 3694 4452
##
## Regression Coefficients:
## Estimate Est.Error l-95% CI
## trait_typebody_size:warm_coldcold -0.27 0.25 -0.76
## trait_typefecundity:warm_coldcold -0.32 0.54 -1.36
## trait_typesurvival:warm_coldcold 0.23 1.39 -2.45
## trait_typebody_size:warm_coldwarm 0.25 0.17 -0.07
## trait_typefecundity:warm_coldwarm 0.07 0.16 -0.25
## trait_typesurvival:warm_coldwarm 0.31 0.35 -0.32
## trait_typebody_size:warm_coldcold:assay_temp_diff 0.00 0.02 -0.04
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.14 0.04 -0.22
## trait_typesurvival:warm_coldcold:assay_temp_diff 0.03 0.04 -0.05
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.02 0.02 -0.02
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.02 0.02 -0.01
## trait_typesurvival:warm_coldwarm:assay_temp_diff -0.02 0.03 -0.07
## trait_typebody_size:warm_coldcold:select_temp_diff 0.04 0.04 -0.03
## trait_typefecundity:warm_coldcold:select_temp_diff 0.01 0.08 -0.15
## trait_typesurvival:warm_coldcold:select_temp_diff -0.02 0.18 -0.37
## trait_typebody_size:warm_coldwarm:select_temp_diff -0.05 0.03 -0.10
## trait_typefecundity:warm_coldwarm:select_temp_diff -0.03 0.03 -0.09
## trait_typesurvival:warm_coldwarm:select_temp_diff -0.01 0.05 -0.12
## u-95% CI Rhat Bulk_ESS
## trait_typebody_size:warm_coldcold 0.22 1.00 3662
## trait_typefecundity:warm_coldcold 0.75 1.00 3726
## trait_typesurvival:warm_coldcold 2.92 1.00 4993
## trait_typebody_size:warm_coldwarm 0.58 1.00 4522
## trait_typefecundity:warm_coldwarm 0.40 1.00 4930
## trait_typesurvival:warm_coldwarm 1.04 1.00 3935
## trait_typebody_size:warm_coldcold:assay_temp_diff 0.05 1.00 3460
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.05 1.00 6312
## trait_typesurvival:warm_coldcold:assay_temp_diff 0.10 1.00 7635
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.05 1.00 4924
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.06 1.00 6597
## trait_typesurvival:warm_coldwarm:assay_temp_diff 0.03 1.00 7964
## trait_typebody_size:warm_coldcold:select_temp_diff 0.12 1.00 3351
## trait_typefecundity:warm_coldcold:select_temp_diff 0.16 1.00 4122
## trait_typesurvival:warm_coldcold:select_temp_diff 0.35 1.00 4928
## trait_typebody_size:warm_coldwarm:select_temp_diff 0.01 1.00 5247
## trait_typefecundity:warm_coldwarm:select_temp_diff 0.03 1.00 5254
## trait_typesurvival:warm_coldwarm:select_temp_diff 0.08 1.00 4311
## Tail_ESS
## trait_typebody_size:warm_coldcold 4684
## trait_typefecundity:warm_coldcold 4819
## trait_typesurvival:warm_coldcold 5571
## trait_typebody_size:warm_coldwarm 4856
## trait_typefecundity:warm_coldwarm 4858
## trait_typesurvival:warm_coldwarm 4510
## trait_typebody_size:warm_coldcold:assay_temp_diff 5041
## trait_typefecundity:warm_coldcold:assay_temp_diff 6073
## trait_typesurvival:warm_coldcold:assay_temp_diff 6521
## trait_typebody_size:warm_coldwarm:assay_temp_diff 5938
## trait_typefecundity:warm_coldwarm:assay_temp_diff 6417
## trait_typesurvival:warm_coldwarm:assay_temp_diff 6546
## trait_typebody_size:warm_coldcold:select_temp_diff 4678
## trait_typefecundity:warm_coldcold:select_temp_diff 5201
## trait_typesurvival:warm_coldcold:select_temp_diff 5669
## trait_typebody_size:warm_coldwarm:select_temp_diff 6029
## trait_typefecundity:warm_coldwarm:select_temp_diff 5784
## trait_typesurvival:warm_coldwarm:select_temp_diff 4512
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnCVR_model_sel_temp_diff, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(Parameter = gsub("trait_type([a-z_]+):warm_cold(cold|warm)", "\\1(\\2)",
Parameter), Parameter = gsub("trait_type([a-z_]+)\\(warm\\):select_temp_diff",
"\\1(warm): select_temp_diff", Parameter)) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size(cold) | -0.273 | -0.764 | 0.221 |
| fecundity(cold) | -0.317 | -1.363 | 0.745 |
| survival(cold) | 0.229 | -2.449 | 2.924 |
| body_size(warm) | 0.247 | -0.072 | 0.576 |
| fecundity(warm) | 0.065 | -0.253 | 0.399 |
| survival(warm) | 0.315 | -0.316 | 1.036 |
| body_size(cold):assay_temp_diff | 0.005 | -0.040 | 0.050 |
| fecundity(cold):assay_temp_diff | -0.135 | -0.223 | -0.050 |
| survival(cold):assay_temp_diff | 0.028 | -0.050 | 0.105 |
| body_size(warm):assay_temp_diff | 0.017 | -0.020 | 0.055 |
| fecundity(warm):assay_temp_diff | 0.024 | -0.009 | 0.055 |
| survival(warm):assay_temp_diff | -0.024 | -0.073 | 0.027 |
| body_size(cold):select_temp_diff | 0.043 | -0.031 | 0.117 |
| fecundity(cold):select_temp_diff | 0.006 | -0.146 | 0.162 |
| survival(cold):select_temp_diff | -0.016 | -0.374 | 0.346 |
| body_size(warm):select_temp_diff | -0.046 | -0.099 | 0.007 |
| fecundity(warm):select_temp_diff | -0.031 | -0.094 | 0.031 |
| survival(warm):select_temp_diff | -0.014 | -0.120 | 0.077 |
Data visualisation
# Generate predictions
emmeans_sel_temp_diff <- as.data.frame(emmeans(
lnCVR_model_sel_temp_diff,
specs = ~ select_temp_diff | trait_type * warm_cold,
at = list(select_temp_diff = seq(min(data$select_temp_diff), max(data$select_temp_diff), by = 0.5),
assay_temp_diff = 0))
) # Conditional effects on assay_temp_diff = 0
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_select_temp_diff = min(select_temp_diff),
max_select_temp_diff = max(select_temp_diff)
)
emmeans_sel_temp_diff$trait_type <- factor(emmeans_sel_temp_diff$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_sel_temp_diff$warm_cold <- factor(emmeans_sel_temp_diff$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_sel_temp_diff <- emmeans_sel_temp_diff %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
select_temp_diff >= min_select_temp_diff,
select_temp_diff <= max_select_temp_diff
)
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = select_temp_diff,
y = lnCVR,
size = 1/sqrt(var_lnCVR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_sel_temp_diff, # Shaded area for credible intervals
aes(x = select_temp_diff,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_sel_temp_diff, # Predicted regression line
aes(y = emmean,
x = select_temp_diff,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -2, -2.75), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Selection temperature difference", y = "lnCVR", col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 25, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 22)) +
guides(fill = "none", size = "none")+
coord_cartesian(ylim = c(-3, 3))
ggsave(file = "fig/lnCVR_sel_temp_diff.png", width = 12, height = 7, dpi = 500)
Number of generations of selection
Model specification
# Model specification
formula <- bf(lnCVR ~ 0 + trait_type:warm_cold +
trait_type:warm_cold:assay_temp_diff +
trait_type:warm_cold:scale(gen_selection) + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnCVR)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnCVR_model_gen_selection <- brm(formula,
family = gaussian(),
data = data,
data2 = list(phylo_matrix = phylo_matrix,
VCV_lnCVR = VCV_lnCVR),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnCVR_model_gen_selection, file = "RData/lnCVR_model_gen_selection.rds")
Model output
# Load model
lnCVR_model_gen_selection <- readRDS("RData/lnCVR_model_gen_selection.rds")
# Display model output
summary(lnCVR_model_gen_selection)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnCVR ~ 0 + trait_type:warm_cold + trait_type:warm_cold:assay_temp_diff + trait_type:warm_cold:scale(gen_selection) + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnCVR)
## Data: data (Number of observations: 476)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 88)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.10 0.05 0.01 0.21 1.00 1136 1843
##
## ~obs (Number of levels: 476)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.40 0.04 0.32 0.48 1.00 1954 4166
##
## ~phylogeny (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.09 0.07 0.00 0.27 1.00 3283 3958
##
## ~ref (Number of levels: 44)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.06 0.05 0.00
## sd(trait_typefecundity) 0.10 0.08 0.00
## sd(trait_typesurvival) 0.57 0.16 0.29
## cor(trait_typebody_size,trait_typefecundity) 0.04 0.50 -0.87
## cor(trait_typebody_size,trait_typesurvival) -0.02 0.49 -0.87
## cor(trait_typefecundity,trait_typesurvival) -0.06 0.44 -0.86
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.18 1.00 3623 4379
## sd(trait_typefecundity) 0.30 1.00 2262 3456
## sd(trait_typesurvival) 0.93 1.00 2722 3789
## cor(trait_typebody_size,trait_typefecundity) 0.89 1.00 4036 5173
## cor(trait_typebody_size,trait_typesurvival) 0.87 1.00 1132 2745
## cor(trait_typefecundity,trait_typesurvival) 0.79 1.00 1659 2645
##
## ~species (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.06 0.05 0.00 0.19 1.00 3793 4342
##
## Regression Coefficients:
## Estimate Est.Error
## trait_typebody_size:warm_coldcold -0.09 0.13
## trait_typefecundity:warm_coldcold -0.38 0.26
## trait_typesurvival:warm_coldcold 0.15 0.39
## trait_typebody_size:warm_coldwarm 0.05 0.11
## trait_typefecundity:warm_coldwarm -0.10 0.11
## trait_typesurvival:warm_coldwarm 0.25 0.23
## trait_typebody_size:warm_coldcold:assay_temp_diff -0.00 0.02
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.14 0.04
## trait_typesurvival:warm_coldcold:assay_temp_diff 0.03 0.04
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.00 0.02
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.02 0.02
## trait_typesurvival:warm_coldwarm:assay_temp_diff -0.03 0.02
## trait_typebody_size:warm_coldcold:scalegen_selection 0.09 0.09
## trait_typefecundity:warm_coldcold:scalegen_selection 0.04 0.15
## trait_typesurvival:warm_coldcold:scalegen_selection -0.05 0.24
## trait_typebody_size:warm_coldwarm:scalegen_selection -0.02 0.06
## trait_typefecundity:warm_coldwarm:scalegen_selection -0.15 0.10
## trait_typesurvival:warm_coldwarm:scalegen_selection 0.02 0.37
## l-95% CI u-95% CI Rhat
## trait_typebody_size:warm_coldcold -0.33 0.18 1.00
## trait_typefecundity:warm_coldcold -0.87 0.14 1.00
## trait_typesurvival:warm_coldcold -0.63 0.93 1.00
## trait_typebody_size:warm_coldwarm -0.16 0.27 1.00
## trait_typefecundity:warm_coldwarm -0.31 0.13 1.00
## trait_typesurvival:warm_coldwarm -0.21 0.71 1.00
## trait_typebody_size:warm_coldcold:assay_temp_diff -0.04 0.04 1.00
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.22 -0.06 1.00
## trait_typesurvival:warm_coldcold:assay_temp_diff -0.05 0.10 1.00
## trait_typebody_size:warm_coldwarm:assay_temp_diff -0.03 0.04 1.00
## trait_typefecundity:warm_coldwarm:assay_temp_diff -0.01 0.05 1.00
## trait_typesurvival:warm_coldwarm:assay_temp_diff -0.08 0.02 1.00
## trait_typebody_size:warm_coldcold:scalegen_selection -0.08 0.26 1.00
## trait_typefecundity:warm_coldcold:scalegen_selection -0.24 0.33 1.00
## trait_typesurvival:warm_coldcold:scalegen_selection -0.51 0.42 1.00
## trait_typebody_size:warm_coldwarm:scalegen_selection -0.14 0.09 1.00
## trait_typefecundity:warm_coldwarm:scalegen_selection -0.35 0.06 1.00
## trait_typesurvival:warm_coldwarm:scalegen_selection -0.72 0.74 1.00
## Bulk_ESS Tail_ESS
## trait_typebody_size:warm_coldcold 3234 3433
## trait_typefecundity:warm_coldcold 4321 4665
## trait_typesurvival:warm_coldcold 3945 4560
## trait_typebody_size:warm_coldwarm 2958 3218
## trait_typefecundity:warm_coldwarm 3365 3715
## trait_typesurvival:warm_coldwarm 3657 4084
## trait_typebody_size:warm_coldcold:assay_temp_diff 3388 4682
## trait_typefecundity:warm_coldcold:assay_temp_diff 6533 6150
## trait_typesurvival:warm_coldcold:assay_temp_diff 5137 5538
## trait_typebody_size:warm_coldwarm:assay_temp_diff 4895 5687
## trait_typefecundity:warm_coldwarm:assay_temp_diff 5555 5630
## trait_typesurvival:warm_coldwarm:assay_temp_diff 6892 5964
## trait_typebody_size:warm_coldcold:scalegen_selection 4020 4607
## trait_typefecundity:warm_coldcold:scalegen_selection 5581 5472
## trait_typesurvival:warm_coldcold:scalegen_selection 5019 5172
## trait_typebody_size:warm_coldwarm:scalegen_selection 5131 5649
## trait_typefecundity:warm_coldwarm:scalegen_selection 4683 4434
## trait_typesurvival:warm_coldwarm:scalegen_selection 4368 5143
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnCVR_model_gen_selection, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(
# Simplify `trait_type` and `warm_cold` interactions
Parameter = gsub("trait_type([a-z_]+):warm_cold(cold|warm)", "\\1(\\2)", Parameter),
# Adjust for assay_temp_diff
Parameter = gsub("trait_type([a-z_]+)\\((cold|warm)\\):assay_temp_diff", "\\1(\\2): assay_temp_diff", Parameter),
# Remove "scale" from gen_selection
Parameter = gsub("scalegen_selection", "gen_selection", Parameter)
) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~ round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size(cold) | -0.089 | -0.331 | 0.181 |
| fecundity(cold) | -0.375 | -0.869 | 0.137 |
| survival(cold) | 0.153 | -0.627 | 0.926 |
| body_size(warm) | 0.050 | -0.156 | 0.271 |
| fecundity(warm) | -0.099 | -0.315 | 0.130 |
| survival(warm) | 0.247 | -0.210 | 0.711 |
| body_size(cold):assay_temp_diff | -0.001 | -0.044 | 0.041 |
| fecundity(cold):assay_temp_diff | -0.140 | -0.217 | -0.064 |
| survival(cold):assay_temp_diff | 0.026 | -0.052 | 0.103 |
| body_size(warm):assay_temp_diff | 0.003 | -0.030 | 0.036 |
| fecundity(warm):assay_temp_diff | 0.021 | -0.010 | 0.053 |
| survival(warm):assay_temp_diff | -0.027 | -0.075 | 0.021 |
| body_size(cold):gen_selection | 0.092 | -0.077 | 0.259 |
| fecundity(cold):gen_selection | 0.045 | -0.244 | 0.326 |
| survival(cold):gen_selection | -0.051 | -0.509 | 0.421 |
| body_size(warm):gen_selection | -0.024 | -0.142 | 0.094 |
| fecundity(warm):gen_selection | -0.145 | -0.351 | 0.057 |
| survival(warm):gen_selection | 0.015 | -0.720 | 0.738 |
Data visualisation
# Generate predictions
emmeans_gen_selection <- as.data.frame(emmeans(
lnCVR_model_gen_selection,
specs = ~ gen_selection | trait_type * warm_cold,
at = list(gen_selection = seq(min(data$gen_selection), max(data$gen_selection), by = 1),
assay_temp_diff = 0))
) # Conditional effects on assay_temp_diff = 0
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_gen_selection = min(gen_selection),
max_gen_selection = max(gen_selection)
)
emmeans_gen_selection$trait_type <- factor(emmeans_gen_selection$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_gen_selection$warm_cold <- factor(emmeans_gen_selection$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_gen_selection <- emmeans_gen_selection %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
gen_selection >= min_gen_selection,
gen_selection <= max_gen_selection
)
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = gen_selection,
y = lnCVR,
size = 1/sqrt(var_lnCVR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_gen_selection, # Shaded area for credible intervals
aes(x = gen_selection,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_gen_selection, # Predicted regression line
aes(y = emmean,
x = gen_selection,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -2, -2.75), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Number of generations of selection", y = "lnCVR", col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 25, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 22)) +
guides(fill = "none", size = "none")+
coord_cartesian(ylim = c(-3, 3),
xlim = c(0, 150))
ggsave(file = "fig/lnCVR_gen_selection.png", width = 12, height = 7, dpi = 500)
Number of generations of common garden
Model specification
# Model specification
formula <- bf(lnCVR ~ 0 + trait_type:warm_cold +
trait_type:warm_cold:assay_temp_diff +
trait_type:warm_cold:scale(gen_common_garden) + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnCVR)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnCVR_model_gen_common_garden <- brm(formula,
family = gaussian(),
data = data,
data2 = list(phylo_matrix = phylo_matrix,
VCV_lnCVR = VCV_lnCVR),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnCVR_model_gen_common_garden, file = "RData/lnCVR_model_gen_common_garden.rds")
Model output
# Load model
lnCVR_model_gen_common_garden <- readRDS("RData/lnCVR_model_gen_common_garden.rds")
# Display model output
summary(lnCVR_model_gen_common_garden)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnCVR ~ 0 + trait_type:warm_cold + trait_type:warm_cold:assay_temp_diff + trait_type:warm_cold:scale(gen_common_garden) + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnCVR)
## Data: data (Number of observations: 476)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 88)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.09 0.05 0.01 0.21 1.00 1293 2743
##
## ~obs (Number of levels: 476)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.40 0.04 0.33 0.48 1.00 2081 3793
##
## ~phylogeny (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.10 0.08 0.00 0.29 1.00 2506 3222
##
## ~ref (Number of levels: 44)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.06 0.05 0.00
## sd(trait_typefecundity) 0.10 0.08 0.00
## sd(trait_typesurvival) 0.55 0.16 0.28
## cor(trait_typebody_size,trait_typefecundity) 0.05 0.49 -0.85
## cor(trait_typebody_size,trait_typesurvival) -0.03 0.49 -0.88
## cor(trait_typefecundity,trait_typesurvival) -0.04 0.45 -0.84
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.18 1.00 3649 4252
## sd(trait_typefecundity) 0.31 1.00 1538 2764
## sd(trait_typesurvival) 0.90 1.00 3047 3663
## cor(trait_typebody_size,trait_typefecundity) 0.89 1.00 3297 5211
## cor(trait_typebody_size,trait_typesurvival) 0.87 1.01 938 2304
## cor(trait_typefecundity,trait_typesurvival) 0.83 1.00 1463 2653
##
## ~species (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.06 0.05 0.00 0.19 1.00 2815 3543
##
## Regression Coefficients:
## Estimate Est.Error
## trait_typebody_size:warm_coldcold -0.01 0.12
## trait_typefecundity:warm_coldcold -0.26 0.20
## trait_typesurvival:warm_coldcold 0.12 0.32
## trait_typebody_size:warm_coldwarm 0.04 0.11
## trait_typefecundity:warm_coldwarm -0.05 0.11
## trait_typesurvival:warm_coldwarm 0.24 0.20
## trait_typebody_size:warm_coldcold:assay_temp_diff -0.01 0.02
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.14 0.04
## trait_typesurvival:warm_coldcold:assay_temp_diff 0.03 0.04
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.00 0.02
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.02 0.02
## trait_typesurvival:warm_coldwarm:assay_temp_diff -0.03 0.02
## trait_typebody_size:warm_coldcold:scalegen_common_garden -0.04 0.49
## trait_typefecundity:warm_coldcold:scalegen_common_garden 0.29 0.85
## trait_typesurvival:warm_coldcold:scalegen_common_garden 0.02 0.94
## trait_typebody_size:warm_coldwarm:scalegen_common_garden -0.31 0.39
## trait_typefecundity:warm_coldwarm:scalegen_common_garden -0.02 0.03
## trait_typesurvival:warm_coldwarm:scalegen_common_garden 0.05 0.81
## l-95% CI u-95% CI Rhat
## trait_typebody_size:warm_coldcold -0.25 0.24 1.00
## trait_typefecundity:warm_coldcold -0.65 0.14 1.00
## trait_typesurvival:warm_coldcold -0.50 0.76 1.00
## trait_typebody_size:warm_coldwarm -0.16 0.26 1.00
## trait_typefecundity:warm_coldwarm -0.26 0.17 1.00
## trait_typesurvival:warm_coldwarm -0.14 0.63 1.00
## trait_typebody_size:warm_coldcold:assay_temp_diff -0.05 0.04 1.00
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.22 -0.06 1.00
## trait_typesurvival:warm_coldcold:assay_temp_diff -0.05 0.10 1.00
## trait_typebody_size:warm_coldwarm:assay_temp_diff -0.03 0.04 1.00
## trait_typefecundity:warm_coldwarm:assay_temp_diff -0.01 0.05 1.00
## trait_typesurvival:warm_coldwarm:assay_temp_diff -0.08 0.02 1.00
## trait_typebody_size:warm_coldcold:scalegen_common_garden -1.00 0.92 1.00
## trait_typefecundity:warm_coldcold:scalegen_common_garden -1.36 2.00 1.00
## trait_typesurvival:warm_coldcold:scalegen_common_garden -1.79 1.87 1.00
## trait_typebody_size:warm_coldwarm:scalegen_common_garden -1.08 0.47 1.00
## trait_typefecundity:warm_coldwarm:scalegen_common_garden -0.08 0.04 1.00
## trait_typesurvival:warm_coldwarm:scalegen_common_garden -1.61 1.63 1.00
## Bulk_ESS Tail_ESS
## trait_typebody_size:warm_coldcold 2851 3100
## trait_typefecundity:warm_coldcold 4004 4578
## trait_typesurvival:warm_coldcold 4694 5317
## trait_typebody_size:warm_coldwarm 3202 3464
## trait_typefecundity:warm_coldwarm 3433 3948
## trait_typesurvival:warm_coldwarm 3213 4539
## trait_typebody_size:warm_coldcold:assay_temp_diff 3002 4645
## trait_typefecundity:warm_coldcold:assay_temp_diff 5549 6001
## trait_typesurvival:warm_coldcold:assay_temp_diff 5136 6370
## trait_typebody_size:warm_coldwarm:assay_temp_diff 4927 6156
## trait_typefecundity:warm_coldwarm:assay_temp_diff 4678 5338
## trait_typesurvival:warm_coldwarm:assay_temp_diff 5295 5614
## trait_typebody_size:warm_coldcold:scalegen_common_garden 5230 5565
## trait_typefecundity:warm_coldcold:scalegen_common_garden 6984 5478
## trait_typesurvival:warm_coldcold:scalegen_common_garden 4118 5376
## trait_typebody_size:warm_coldwarm:scalegen_common_garden 5169 5857
## trait_typefecundity:warm_coldwarm:scalegen_common_garden 6263 6362
## trait_typesurvival:warm_coldwarm:scalegen_common_garden 4182 4925
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnCVR_model_gen_common_garden, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(
# Simplify `trait_type` and `warm_cold` interactions
Parameter = gsub("trait_type([a-z_]+):warm_cold(cold|warm)", "\\1(\\2)", Parameter),
# Adjust for assay_temp_diff
Parameter = gsub("trait_type([a-z_]+)\\((cold|warm)\\):assay_temp_diff", "\\1(\\2): assay_temp_diff", Parameter),
# Remove "scale" from gen_common_garden
Parameter = gsub("scalegen_common_garden", "gen_common_garden", Parameter)
) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~ round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size(cold) | -0.014 | -0.245 | 0.242 |
| fecundity(cold) | -0.260 | -0.648 | 0.145 |
| survival(cold) | 0.124 | -0.500 | 0.759 |
| body_size(warm) | 0.043 | -0.162 | 0.265 |
| fecundity(warm) | -0.048 | -0.256 | 0.165 |
| survival(warm) | 0.244 | -0.138 | 0.634 |
| body_size(cold):assay_temp_diff | -0.008 | -0.049 | 0.035 |
| fecundity(cold):assay_temp_diff | -0.141 | -0.218 | -0.063 |
| survival(cold):assay_temp_diff | 0.027 | -0.048 | 0.104 |
| body_size(warm):assay_temp_diff | 0.001 | -0.032 | 0.036 |
| fecundity(warm):assay_temp_diff | 0.018 | -0.014 | 0.049 |
| survival(warm):assay_temp_diff | -0.026 | -0.075 | 0.023 |
| body_size(cold):gen_common_garden | -0.041 | -1.000 | 0.917 |
| fecundity(cold):gen_common_garden | 0.290 | -1.362 | 2.000 |
| survival(cold):gen_common_garden | 0.021 | -1.795 | 1.866 |
| body_size(warm):gen_common_garden | -0.314 | -1.079 | 0.466 |
| fecundity(warm):gen_common_garden | -0.022 | -0.079 | 0.035 |
| survival(warm):gen_common_garden | 0.046 | -1.608 | 1.627 |
Data visualisation
# Generate predictions
emmeans_gen_common_garden <- as.data.frame(emmeans(
lnCVR_model_gen_common_garden,
specs = ~ gen_common_garden | trait_type * warm_cold,
at = list(gen_common_garden = seq(min(data$gen_common_garden), max(data$gen_common_garden), by = 1),
assay_temp_diff = 0))
) # Conditional effects on assay_temp_diff = 0
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_gen_common_garden = min(gen_common_garden),
max_gen_common_garden = max(gen_common_garden)
)
emmeans_gen_common_garden$trait_type <- factor(emmeans_gen_common_garden$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_gen_common_garden$warm_cold <- factor(emmeans_gen_common_garden$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_gen_common_garden <- emmeans_gen_common_garden %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
gen_common_garden >= min_gen_common_garden,
gen_common_garden <= max_gen_common_garden
)
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = gen_common_garden,
y = lnCVR,
size = 1/sqrt(var_lnCVR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_gen_common_garden, # Shaded area for credible intervals
aes(x = gen_common_garden,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_gen_common_garden, # Predicted regression line
aes(y = emmean,
x = gen_common_garden,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -2, -2.75), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Number of generations of common garden", y = "lnCVR", col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 25, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 22)) +
guides(fill = "none", size = "none")+
coord_cartesian(ylim = c(-3, 3),
xlim = c(0, 12))
ggsave(file = "fig/lnCVR_gen_common_garden.png", width = 12, height = 7, dpi = 500)
Population size
Model specification
# Model specification
formula <- bf(lnCVR ~ 0 + trait_type:warm_cold +
trait_type:warm_cold:assay_temp_diff +
trait_type:warm_cold:scale(pop_size) + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnCVR)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnCVR_model_pop_size <- brm(formula,
family = gaussian(),
data = data,
data2 = list(phylo_matrix = phylo_matrix,
VCV_lnCVR = VCV_lnCVR),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnCVR_model_pop_size, file = "RData/lnCVR_model_pop_size.rds")
Model output
# Load model
lnCVR_model_pop_size <- readRDS("RData/lnCVR_model_pop_size.rds")
# Display model output
summary(lnCVR_model_pop_size)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnCVR ~ 0 + trait_type:warm_cold + trait_type:warm_cold:assay_temp_diff + trait_type:warm_cold:scale(pop_size) + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnCVR)
## Data: data (Number of observations: 426)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 79)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.08 0.05 0.00 0.19 1.00 1928 3870
##
## ~obs (Number of levels: 426)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.26 0.03 0.20 0.32 1.00 3563 4726
##
## ~phylogeny (Number of levels: 22)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.09 0.08 0.00 0.28 1.00 3836 5030
##
## ~ref (Number of levels: 41)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.07 0.06 0.00
## sd(trait_typefecundity) 0.17 0.10 0.01
## sd(trait_typesurvival) 0.31 0.13 0.06
## cor(trait_typebody_size,trait_typefecundity) -0.01 0.48 -0.86
## cor(trait_typebody_size,trait_typesurvival) -0.07 0.48 -0.88
## cor(trait_typefecundity,trait_typesurvival) 0.09 0.48 -0.82
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.21 1.00 3844 4628
## sd(trait_typefecundity) 0.40 1.00 1859 3255
## sd(trait_typesurvival) 0.59 1.00 2879 1949
## cor(trait_typebody_size,trait_typefecundity) 0.87 1.00 3526 5348
## cor(trait_typebody_size,trait_typesurvival) 0.84 1.00 2377 4567
## cor(trait_typefecundity,trait_typesurvival) 0.90 1.00 3260 5476
##
## ~species (Number of levels: 22)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.07 0.06 0.00 0.22 1.00 3810 5167
##
## Regression Coefficients:
## Estimate Est.Error l-95% CI
## trait_typebody_size:warm_coldcold 0.02 0.12 -0.21
## trait_typefecundity:warm_coldcold -0.12 0.20 -0.50
## trait_typesurvival:warm_coldcold 0.05 0.25 -0.44
## trait_typebody_size:warm_coldwarm -0.01 0.10 -0.21
## trait_typefecundity:warm_coldwarm 0.00 0.11 -0.22
## trait_typesurvival:warm_coldwarm 0.01 0.15 -0.28
## trait_typebody_size:warm_coldcold:assay_temp_diff 0.01 0.02 -0.03
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.12 0.04 -0.19
## trait_typesurvival:warm_coldcold:assay_temp_diff 0.03 0.03 -0.04
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.01 0.02 -0.02
## trait_typefecundity:warm_coldwarm:assay_temp_diff -0.01 0.02 -0.04
## trait_typesurvival:warm_coldwarm:assay_temp_diff 0.00 0.02 -0.04
## trait_typebody_size:warm_coldcold:scalepop_size -0.12 0.07 -0.27
## trait_typefecundity:warm_coldcold:scalepop_size -0.17 0.16 -0.47
## trait_typesurvival:warm_coldcold:scalepop_size -0.06 0.24 -0.53
## trait_typebody_size:warm_coldwarm:scalepop_size -0.01 0.05 -0.10
## trait_typefecundity:warm_coldwarm:scalepop_size -0.07 0.07 -0.22
## trait_typesurvival:warm_coldwarm:scalepop_size -0.09 0.11 -0.30
## u-95% CI Rhat Bulk_ESS
## trait_typebody_size:warm_coldcold 0.26 1.00 4233
## trait_typefecundity:warm_coldcold 0.28 1.00 6196
## trait_typesurvival:warm_coldcold 0.54 1.00 6169
## trait_typebody_size:warm_coldwarm 0.20 1.00 4049
## trait_typefecundity:warm_coldwarm 0.22 1.00 4479
## trait_typesurvival:warm_coldwarm 0.31 1.00 5233
## trait_typebody_size:warm_coldcold:assay_temp_diff 0.04 1.00 5531
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.05 1.00 7889
## trait_typesurvival:warm_coldcold:assay_temp_diff 0.09 1.00 9465
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.04 1.00 7338
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.02 1.00 9093
## trait_typesurvival:warm_coldwarm:assay_temp_diff 0.04 1.00 9999
## trait_typebody_size:warm_coldcold:scalepop_size 0.03 1.00 4773
## trait_typefecundity:warm_coldcold:scalepop_size 0.16 1.00 5944
## trait_typesurvival:warm_coldcold:scalepop_size 0.42 1.00 8192
## trait_typebody_size:warm_coldwarm:scalepop_size 0.09 1.00 7127
## trait_typefecundity:warm_coldwarm:scalepop_size 0.07 1.00 6452
## trait_typesurvival:warm_coldwarm:scalepop_size 0.14 1.00 6860
## Tail_ESS
## trait_typebody_size:warm_coldcold 4549
## trait_typefecundity:warm_coldcold 5844
## trait_typesurvival:warm_coldcold 6334
## trait_typebody_size:warm_coldwarm 4031
## trait_typefecundity:warm_coldwarm 4427
## trait_typesurvival:warm_coldwarm 4876
## trait_typebody_size:warm_coldcold:assay_temp_diff 5718
## trait_typefecundity:warm_coldcold:assay_temp_diff 6041
## trait_typesurvival:warm_coldcold:assay_temp_diff 6331
## trait_typebody_size:warm_coldwarm:assay_temp_diff 6816
## trait_typefecundity:warm_coldwarm:assay_temp_diff 6546
## trait_typesurvival:warm_coldwarm:assay_temp_diff 6555
## trait_typebody_size:warm_coldcold:scalepop_size 5270
## trait_typefecundity:warm_coldcold:scalepop_size 5760
## trait_typesurvival:warm_coldcold:scalepop_size 6860
## trait_typebody_size:warm_coldwarm:scalepop_size 6336
## trait_typefecundity:warm_coldwarm:scalepop_size 5785
## trait_typesurvival:warm_coldwarm:scalepop_size 5831
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnCVR_model_pop_size, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(
# Simplify `trait_type` and `warm_cold` interactions
Parameter = gsub("trait_type([a-z_]+):warm_cold(cold|warm)", "\\1(\\2)", Parameter),
# Adjust for assay_temp_diff
Parameter = gsub("trait_type([a-z_]+)\\((cold|warm)\\):assay_temp_diff", "\\1(\\2): assay_temp_diff", Parameter),
# Remove "scale" from pop_size
Parameter = gsub("scalepop_size", "pop_size", Parameter)
) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~ round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size(cold) | 0.018 | -0.211 | 0.256 |
| fecundity(cold) | -0.116 | -0.503 | 0.278 |
| survival(cold) | 0.050 | -0.439 | 0.543 |
| body_size(warm) | -0.007 | -0.211 | 0.201 |
| fecundity(warm) | 0.003 | -0.219 | 0.220 |
| survival(warm) | 0.011 | -0.282 | 0.315 |
| body_size(cold):assay_temp_diff | 0.007 | -0.028 | 0.043 |
| fecundity(cold):assay_temp_diff | -0.119 | -0.191 | -0.048 |
| survival(cold):assay_temp_diff | 0.028 | -0.038 | 0.094 |
| body_size(warm):assay_temp_diff | 0.008 | -0.021 | 0.037 |
| fecundity(warm):assay_temp_diff | -0.011 | -0.041 | 0.020 |
| survival(warm):assay_temp_diff | 0.001 | -0.042 | 0.042 |
| body_size(cold):pop_size | -0.119 | -0.267 | 0.026 |
| fecundity(cold):pop_size | -0.166 | -0.472 | 0.160 |
| survival(cold):pop_size | -0.055 | -0.527 | 0.417 |
| body_size(warm):pop_size | -0.005 | -0.102 | 0.092 |
| fecundity(warm):pop_size | -0.072 | -0.217 | 0.069 |
| survival(warm):pop_size | -0.086 | -0.301 | 0.137 |
Data visualisation
# Generate predictions
emmeans_pop_size <- as.data.frame(emmeans(
lnCVR_model_pop_size,
specs = ~ pop_size | trait_type * warm_cold,
at = list(pop_size = seq(min(data$pop_size, na.rm=T), max(data$pop_size, na.rm=T), by = 50),
assay_temp_diff = 0))
) # Conditional effects on assay_temp_diff = 0
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_pop_size = min(pop_size, na.rm=T),
max_pop_size = max(pop_size, na.rm=T)
)
emmeans_pop_size$trait_type <- factor(emmeans_pop_size$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_pop_size$warm_cold <- factor(emmeans_pop_size$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_pop_size <- emmeans_pop_size %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
pop_size >= min_pop_size,
pop_size <= max_pop_size
)
# Calculate sample sizes and study counts for population size
sample_sizes_pop_size <- data %>%
filter(is.na(pop_size)==FALSE) %>%
group_by(trait_type, warm_cold) %>%
summarise(
estimates = n(),
studies = n_distinct(ref)
)
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = pop_size,
y = lnCVR,
size = 1/sqrt(var_lnCVR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_pop_size, # Shaded area for credible intervals
aes(x = pop_size,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_pop_size, # Predicted regression line
aes(y = emmean,
x = pop_size,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes_pop_size,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -2, -2.75), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Population size", y = "lnCVR", col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 25, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 22)) +
guides(fill = "none", size = "none")+
coord_cartesian(ylim = c(-3, 3))
ggsave(file = "fig/lnCVR_pop_size.png", width = 12, height = 7, dpi = 500)
All moderators
Model specification
# Model specification
formula <- bf(lnCVR ~ 0 + trait_type:warm_cold +
trait_type:warm_cold:assay_temp_diff +
trait_type:warm_cold:scale(select_temp_diff) +
trait_type:warm_cold:scale(gen_selection) +
trait_type:warm_cold:scale(gen_common_garden) + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnCVR)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnCVR_model_all_moderators <- brm(formula,
family = gaussian(),
data = data,
data2 = list(phylo_matrix = phylo_matrix,
VCV_lnCVR = VCV_lnCVR),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnCVR_model_all_moderators, file = "RData/lnCVR_model_all_moderators.rds")
Model output
# Load model
lnCVR_model_all_moderators <- readRDS("RData/lnCVR_model_all_moderators.rds")
# Display model output
summary(lnCVR_model_all_moderators)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnCVR ~ 0 + trait_type:warm_cold + trait_type:warm_cold:assay_temp_diff + trait_type:warm_cold:scale(select_temp_diff) + trait_type:warm_cold:scale(gen_selection) + trait_type:warm_cold:scale(gen_common_garden) + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnCVR)
## Data: data (Number of observations: 476)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 88)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.10 0.06 0.01 0.22 1.00 1377 3121
##
## ~obs (Number of levels: 476)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.40 0.04 0.32 0.48 1.00 2187 4271
##
## ~phylogeny (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.10 0.08 0.00 0.29 1.00 4220 4874
##
## ~ref (Number of levels: 44)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.07 0.05 0.00
## sd(trait_typefecundity) 0.12 0.10 0.00
## sd(trait_typesurvival) 0.67 0.20 0.34
## cor(trait_typebody_size,trait_typefecundity) 0.01 0.50 -0.88
## cor(trait_typebody_size,trait_typesurvival) -0.01 0.50 -0.88
## cor(trait_typefecundity,trait_typesurvival) -0.01 0.45 -0.84
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.20 1.00 4609 4688
## sd(trait_typefecundity) 0.36 1.00 1890 3738
## sd(trait_typesurvival) 1.12 1.00 2811 4651
## cor(trait_typebody_size,trait_typefecundity) 0.87 1.00 4835 5800
## cor(trait_typebody_size,trait_typesurvival) 0.88 1.00 1376 3874
## cor(trait_typefecundity,trait_typesurvival) 0.83 1.00 1920 3925
##
## ~species (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.06 0.05 0.00 0.19 1.00 4809 5078
##
## Regression Coefficients:
## Estimate Est.Error
## trait_typebody_size:warm_coldcold -0.09 0.13
## trait_typefecundity:warm_coldcold -0.40 0.27
## trait_typesurvival:warm_coldcold 0.22 0.57
## trait_typebody_size:warm_coldwarm -0.02 0.11
## trait_typefecundity:warm_coldwarm -0.12 0.12
## trait_typesurvival:warm_coldwarm 0.26 0.28
## trait_typebody_size:warm_coldcold:assay_temp_diff 0.01 0.02
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.14 0.05
## trait_typesurvival:warm_coldcold:assay_temp_diff 0.03 0.04
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.02 0.02
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.02 0.02
## trait_typesurvival:warm_coldwarm:assay_temp_diff -0.03 0.03
## trait_typebody_size:warm_coldcold:scaleselect_temp_diff 0.10 0.13
## trait_typefecundity:warm_coldcold:scaleselect_temp_diff 0.00 0.26
## trait_typesurvival:warm_coldcold:scaleselect_temp_diff 0.12 1.09
## trait_typebody_size:warm_coldwarm:scaleselect_temp_diff -0.15 0.09
## trait_typefecundity:warm_coldwarm:scaleselect_temp_diff -0.06 0.11
## trait_typesurvival:warm_coldwarm:scaleselect_temp_diff -0.09 0.19
## trait_typebody_size:warm_coldcold:scalegen_selection 0.07 0.10
## trait_typefecundity:warm_coldcold:scalegen_selection 0.09 0.17
## trait_typesurvival:warm_coldcold:scalegen_selection -0.13 0.48
## trait_typebody_size:warm_coldwarm:scalegen_selection -0.04 0.06
## trait_typefecundity:warm_coldwarm:scalegen_selection -0.12 0.12
## trait_typesurvival:warm_coldwarm:scalegen_selection 0.05 0.47
## trait_typebody_size:warm_coldcold:scalegen_common_garden -0.06 0.55
## trait_typefecundity:warm_coldcold:scalegen_common_garden 0.51 0.97
## trait_typesurvival:warm_coldcold:scalegen_common_garden -0.33 1.56
## trait_typebody_size:warm_coldwarm:scalegen_common_garden -0.24 0.40
## trait_typefecundity:warm_coldwarm:scalegen_common_garden -0.01 0.03
## trait_typesurvival:warm_coldwarm:scalegen_common_garden 0.04 0.95
## l-95% CI u-95% CI Rhat
## trait_typebody_size:warm_coldcold -0.34 0.18 1.00
## trait_typefecundity:warm_coldcold -0.93 0.14 1.00
## trait_typesurvival:warm_coldcold -0.92 1.35 1.00
## trait_typebody_size:warm_coldwarm -0.24 0.21 1.00
## trait_typefecundity:warm_coldwarm -0.36 0.13 1.00
## trait_typesurvival:warm_coldwarm -0.28 0.81 1.00
## trait_typebody_size:warm_coldcold:assay_temp_diff -0.04 0.05 1.00
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.23 -0.05 1.00
## trait_typesurvival:warm_coldcold:assay_temp_diff -0.05 0.11 1.00
## trait_typebody_size:warm_coldwarm:assay_temp_diff -0.02 0.05 1.00
## trait_typefecundity:warm_coldwarm:assay_temp_diff -0.01 0.06 1.00
## trait_typesurvival:warm_coldwarm:assay_temp_diff -0.08 0.03 1.00
## trait_typebody_size:warm_coldcold:scaleselect_temp_diff -0.15 0.37 1.00
## trait_typefecundity:warm_coldcold:scaleselect_temp_diff -0.51 0.51 1.00
## trait_typesurvival:warm_coldcold:scaleselect_temp_diff -2.07 2.26 1.00
## trait_typebody_size:warm_coldwarm:scaleselect_temp_diff -0.32 0.03 1.00
## trait_typefecundity:warm_coldwarm:scaleselect_temp_diff -0.28 0.15 1.00
## trait_typesurvival:warm_coldwarm:scaleselect_temp_diff -0.48 0.26 1.00
## trait_typebody_size:warm_coldcold:scalegen_selection -0.13 0.27 1.00
## trait_typefecundity:warm_coldcold:scalegen_selection -0.26 0.42 1.00
## trait_typesurvival:warm_coldcold:scalegen_selection -1.07 0.82 1.00
## trait_typebody_size:warm_coldwarm:scalegen_selection -0.16 0.08 1.00
## trait_typefecundity:warm_coldwarm:scalegen_selection -0.36 0.12 1.00
## trait_typesurvival:warm_coldwarm:scalegen_selection -0.90 0.97 1.00
## trait_typebody_size:warm_coldcold:scalegen_common_garden -1.15 1.00 1.00
## trait_typefecundity:warm_coldcold:scalegen_common_garden -1.40 2.43 1.00
## trait_typesurvival:warm_coldcold:scalegen_common_garden -3.43 2.65 1.00
## trait_typebody_size:warm_coldwarm:scalegen_common_garden -1.02 0.56 1.00
## trait_typefecundity:warm_coldwarm:scalegen_common_garden -0.07 0.05 1.00
## trait_typesurvival:warm_coldwarm:scalegen_common_garden -1.82 1.91 1.00
## Bulk_ESS Tail_ESS
## trait_typebody_size:warm_coldcold 5633 5252
## trait_typefecundity:warm_coldcold 6562 6145
## trait_typesurvival:warm_coldcold 6185 5366
## trait_typebody_size:warm_coldwarm 4994 5280
## trait_typefecundity:warm_coldwarm 5866 5615
## trait_typesurvival:warm_coldwarm 5065 5349
## trait_typebody_size:warm_coldcold:assay_temp_diff 4449 6250
## trait_typefecundity:warm_coldcold:assay_temp_diff 9299 6722
## trait_typesurvival:warm_coldcold:assay_temp_diff 9106 6383
## trait_typebody_size:warm_coldwarm:assay_temp_diff 6480 6607
## trait_typefecundity:warm_coldwarm:assay_temp_diff 7267 6527
## trait_typesurvival:warm_coldwarm:assay_temp_diff 10561 6446
## trait_typebody_size:warm_coldcold:scaleselect_temp_diff 7028 6159
## trait_typefecundity:warm_coldcold:scaleselect_temp_diff 7620 6412
## trait_typesurvival:warm_coldcold:scaleselect_temp_diff 5130 4539
## trait_typebody_size:warm_coldwarm:scaleselect_temp_diff 8813 7237
## trait_typefecundity:warm_coldwarm:scaleselect_temp_diff 6749 5735
## trait_typesurvival:warm_coldwarm:scaleselect_temp_diff 6028 5134
## trait_typebody_size:warm_coldcold:scalegen_selection 6995 6454
## trait_typefecundity:warm_coldcold:scalegen_selection 8805 6229
## trait_typesurvival:warm_coldcold:scalegen_selection 5874 5486
## trait_typebody_size:warm_coldwarm:scalegen_selection 8712 6476
## trait_typefecundity:warm_coldwarm:scalegen_selection 7044 5964
## trait_typesurvival:warm_coldwarm:scalegen_selection 5684 4810
## trait_typebody_size:warm_coldcold:scalegen_common_garden 10553 7087
## trait_typefecundity:warm_coldcold:scalegen_common_garden 10086 6398
## trait_typesurvival:warm_coldcold:scalegen_common_garden 5725 5479
## trait_typebody_size:warm_coldwarm:scalegen_common_garden 9508 6315
## trait_typefecundity:warm_coldwarm:scalegen_common_garden 9450 6865
## trait_typesurvival:warm_coldwarm:scalegen_common_garden 8083 6912
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnCVR_model_all_moderators, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(
# Simplify `trait_type` and `warm_cold` interactions
Parameter = gsub("trait_type([a-z_]+):warm_cold(cold|warm)", "\\1(\\2)", Parameter),
# Adjust for assay_temp_diff
Parameter = gsub("trait_type([a-z_]+)\\((cold|warm)\\):assay_temp_diff", "\\1(\\2): assay_temp_diff", Parameter),
# Remove "scale" from all moderator variables
Parameter = gsub("scale", "", Parameter)
) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~ round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size(cold) | -0.090 | -0.344 | 0.182 |
| fecundity(cold) | -0.399 | -0.930 | 0.142 |
| survival(cold) | 0.223 | -0.918 | 1.350 |
| body_size(warm) | -0.017 | -0.243 | 0.214 |
| fecundity(warm) | -0.119 | -0.364 | 0.133 |
| survival(warm) | 0.265 | -0.278 | 0.813 |
| body_size(cold):assay_temp_diff | 0.006 | -0.038 | 0.053 |
| fecundity(cold):assay_temp_diff | -0.138 | -0.226 | -0.051 |
| survival(cold):assay_temp_diff | 0.030 | -0.050 | 0.110 |
| body_size(warm):assay_temp_diff | 0.015 | -0.022 | 0.054 |
| fecundity(warm):assay_temp_diff | 0.024 | -0.009 | 0.057 |
| survival(warm):assay_temp_diff | -0.025 | -0.076 | 0.026 |
| body_size(cold):select_temp_diff | 0.102 | -0.153 | 0.370 |
| fecundity(cold):select_temp_diff | 0.004 | -0.506 | 0.511 |
| survival(cold):select_temp_diff | 0.117 | -2.070 | 2.265 |
| body_size(warm):select_temp_diff | -0.149 | -0.325 | 0.027 |
| fecundity(warm):select_temp_diff | -0.060 | -0.278 | 0.147 |
| survival(warm):select_temp_diff | -0.085 | -0.481 | 0.261 |
| body_size(cold):gen_selection | 0.069 | -0.126 | 0.269 |
| fecundity(cold):gen_selection | 0.088 | -0.258 | 0.420 |
| survival(cold):gen_selection | -0.130 | -1.072 | 0.824 |
| body_size(warm):gen_selection | -0.042 | -0.164 | 0.079 |
| fecundity(warm):gen_selection | -0.122 | -0.359 | 0.118 |
| survival(warm):gen_selection | 0.047 | -0.898 | 0.971 |
| body_size(cold):gen_common_garden | -0.063 | -1.147 | 0.997 |
| fecundity(cold):gen_common_garden | 0.508 | -1.400 | 2.428 |
| survival(cold):gen_common_garden | -0.334 | -3.426 | 2.647 |
| body_size(warm):gen_common_garden | -0.236 | -1.020 | 0.562 |
| fecundity(warm):gen_common_garden | -0.011 | -0.073 | 0.051 |
| survival(warm):gen_common_garden | 0.038 | -1.822 | 1.914 |
# Generate predicted values at different assay temperatures
emmeans_full_model <- as.data.frame(emmeans(
lnCVR_model_all_moderators,
specs = ~ assay_temp_diff * select_temp_diff * gen_selection * gen_common_garden | trait_type * warm_cold,
at = list(assay_temp_diff = c(-8.5, -4, 0, 4, 5, -5, 17, 20)) # Predict at unique min, control, max values
))
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_assay = min(assay_temp_diff),
max_assay = max(assay_temp_diff),
control_assay = 0
)
emmeans_full_model$trait_type <- factor(emmeans_full_model$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_full_model$warm_cold <- factor(emmeans_full_model$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_full_model %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
assay_temp_diff == min_assay |
assay_temp_diff == max_assay |
assay_temp_diff == control_assay) %>%
dplyr::select(-min_assay, -max_assay, -control_assay) %>%
mutate(percentage_change = (exp(emmean) - 1) * 100,
percentage_lower_HPD = (exp(lower.HPD) - 1) * 100,
percentage_upper_HPD = (exp(upper.HPD) - 1) * 100) %>%
mutate(across(c(emmean,
lower.HPD,
upper.HPD,
percentage_change,
percentage_lower_HPD,
percentage_upper_HPD), ~ round(., 3))) %>% # Round numbers to 3 decimal points
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| assay_temp_diff | select_temp_diff | gen_selection | gen_common_garden | trait_type | warm_cold | emmean | lower.HPD | upper.HPD | percentage_change | percentage_lower_HPD | percentage_upper_HPD |
|---|---|---|---|---|---|---|---|---|---|---|---|
| -8.5 | 5.64645 | 29.0084 | 2.710084 | Body size | Cold | -0.140 | -0.594 | 0.329 | -13.104 | -44.796 | 38.892 |
| 0.0 | 5.64645 | 29.0084 | 2.710084 | Body size | Cold | -0.092 | -0.349 | 0.172 | -8.758 | -29.473 | 18.818 |
| 5.0 | 5.64645 | 29.0084 | 2.710084 | Body size | Cold | -0.062 | -0.417 | 0.278 | -6.047 | -34.070 | 31.988 |
| -8.5 | 5.64645 | 29.0084 | 2.710084 | Fecundity | Cold | 0.767 | -0.165 | 1.688 | 115.263 | -15.191 | 440.604 |
| 0.0 | 5.64645 | 29.0084 | 2.710084 | Fecundity | Cold | -0.400 | -0.941 | 0.125 | -32.991 | -60.993 | 13.367 |
| 5.0 | 5.64645 | 29.0084 | 2.710084 | Fecundity | Cold | -1.090 | -1.785 | -0.435 | -66.371 | -83.222 | -35.277 |
| -8.5 | 5.64645 | 29.0084 | 2.710084 | Survival | Cold | -0.036 | -1.311 | 1.247 | -3.495 | -73.041 | 248.077 |
| 0.0 | 5.64645 | 29.0084 | 2.710084 | Survival | Cold | 0.222 | -0.910 | 1.353 | 24.911 | -59.739 | 286.737 |
| 4.0 | 5.64645 | 29.0084 | 2.710084 | Survival | Cold | 0.344 | -0.824 | 1.561 | 40.996 | -56.111 | 376.140 |
| 0.0 | 5.64645 | 29.0084 | 2.710084 | Body size | Warm | -0.019 | -0.245 | 0.211 | -1.860 | -21.713 | 23.475 |
| -5.0 | 5.64645 | 29.0084 | 2.710084 | Body size | Warm | -0.092 | -0.450 | 0.243 | -8.819 | -36.239 | 27.506 |
| 17.0 | 5.64645 | 29.0084 | 2.710084 | Body size | Warm | 0.235 | -0.342 | 0.815 | 26.499 | -28.962 | 125.950 |
| -4.0 | 5.64645 | 29.0084 | 2.710084 | Fecundity | Warm | -0.215 | -0.545 | 0.117 | -19.318 | -42.019 | 12.451 |
| 0.0 | 5.64645 | 29.0084 | 2.710084 | Fecundity | Warm | -0.122 | -0.369 | 0.126 | -11.486 | -30.832 | 13.427 |
| 20.0 | 5.64645 | 29.0084 | 2.710084 | Fecundity | Warm | 0.357 | -0.227 | 0.938 | 42.928 | -20.273 | 155.540 |
| 0.0 | 5.64645 | 29.0084 | 2.710084 | Survival | Warm | 0.260 | -0.297 | 0.790 | 29.726 | -25.718 | 120.370 |
| -5.0 | 5.64645 | 29.0084 | 2.710084 | Survival | Warm | 0.385 | -0.268 | 1.036 | 46.921 | -23.532 | 181.703 |
| 17.0 | 5.64645 | 29.0084 | 2.710084 | Survival | Warm | -0.160 | -1.057 | 0.798 | -14.760 | -65.256 | 122.177 |
Data visualisation
Marginal means
# Generate predictions at assay_temp_diff = 0, or 5 degrees below or above the control temperatures
emmeans_full_model <- as.data.frame(emmeans(
lnCVR_model_all_moderators,
specs = ~ assay_temp_diff * select_temp_diff * gen_selection * gen_common_garden | trait_type * warm_cold,
at = list(assay_temp_diff = c(-5, 0, 5) # Re-acclimated to control, or 5 degrees above/below the contol
)))
# Rename variable levels
emmeans_full_model$trait_type <- factor(emmeans_full_model$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_full_model$warm_cold <- factor(emmeans_full_model$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
emmeans_full_model
## assay_temp_diff select_temp_diff gen_selection gen_common_garden trait_type
## 1 -5 5.64645 29.0084 2.710084 Body size
## 2 0 5.64645 29.0084 2.710084 Body size
## 3 5 5.64645 29.0084 2.710084 Body size
## 4 -5 5.64645 29.0084 2.710084 Fecundity
## 5 0 5.64645 29.0084 2.710084 Fecundity
## 6 5 5.64645 29.0084 2.710084 Fecundity
## 7 -5 5.64645 29.0084 2.710084 Survival
## 8 0 5.64645 29.0084 2.710084 Survival
## 9 5 5.64645 29.0084 2.710084 Survival
## 10 -5 5.64645 29.0084 2.710084 Body size
## 11 0 5.64645 29.0084 2.710084 Body size
## 12 5 5.64645 29.0084 2.710084 Body size
## 13 -5 5.64645 29.0084 2.710084 Fecundity
## 14 0 5.64645 29.0084 2.710084 Fecundity
## 15 5 5.64645 29.0084 2.710084 Fecundity
## 16 -5 5.64645 29.0084 2.710084 Survival
## 17 0 5.64645 29.0084 2.710084 Survival
## 18 5 5.64645 29.0084 2.710084 Survival
## warm_cold emmean lower.HPD upper.HPD
## 1 Cold -0.121334501 -0.4517610 0.2373984
## 2 Cold -0.091659735 -0.3491781 0.1724248
## 3 Cold -0.062374689 -0.4165708 0.2775442
## 4 Cold 0.283330182 -0.3821255 1.0269214
## 5 Cold -0.400345363 -0.9414361 0.1254571
## 6 Cold -1.089791868 -1.7851123 -0.4350608
## 7 Cold 0.069555305 -1.1210984 1.2306247
## 8 Cold 0.222434159 -0.9097910 1.3525739
## 9 Cold 0.371748070 -0.8646504 1.5842286
## 10 Warm -0.092321044 -0.4500300 0.2429957
## 11 Warm -0.018771553 -0.2447877 0.2108647
## 12 Warm 0.056265266 -0.1755918 0.2826457
## 13 Warm -0.238330760 -0.5884738 0.1208489
## 14 Warm -0.122005369 -0.3686250 0.1259859
## 15 Warm -0.003044981 -0.2146602 0.2373331
## 16 Warm 0.384727184 -0.2682933 1.0356829
## 17 Warm 0.260257792 -0.2973033 0.7901396
## 18 Warm 0.141428516 -0.4144538 0.6911635
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75,
lwd=1) +
geom_quasirandom(data = data, # Plot effect sizes, scaled by precision
aes(x = trait_type:warm_cold,
y = lnCVR,
fill = trait_type:warm_cold,
size = 1/sqrt(var_lnCVR)),
color = "black",
shape = 21,
width = 0.3,
alpha = 0.3) +
geom_errorbar(data = filter(emmeans_full_model,
assay_temp_diff == "-5"), # 5 degrees below control (background)
aes(x = trait_type:warm_cold,
ymin = lower.HPD,
ymax = upper.HPD),
color="white",
size = 2.75,
width = 0.16,
position = position_nudge(x=-0.25)) +
geom_point(data = filter(emmeans_full_model,
assay_temp_diff == "-5"), # 5 degrees below control (background)
aes(x = trait_type:warm_cold,
y = emmean),
shape = 21,
size = 4.5,
stroke = 2,
color="white",
fill = "white",
position = position_nudge(x=-0.25)) +
geom_errorbar(data = filter(emmeans_full_model,
assay_temp_diff == "-5"), # 5 degrees below control (credible intervals)
aes(x = trait_type:warm_cold,
ymin = lower.HPD,
ymax = upper.HPD),
color="#06B4BA",
size = 2,
width = 0.12,
position = position_nudge(x=-0.25)) +
geom_point(data = filter(emmeans_full_model,
assay_temp_diff == "-5"), # 5 degrees below control (point estimates)
aes(x = trait_type:warm_cold,
y = emmean),
shape = 21,
size = 3.5,
stroke = 2,
color="#06B4BA",
fill = "white",
position = position_nudge(x=-0.25)) +
geom_errorbar(data = filter(emmeans_full_model,
assay_temp_diff == "0"), # control temperature (background)
aes(x = trait_type:warm_cold,
ymin = lower.HPD,
ymax = upper.HPD),
color="white",
size = 2.75,
width = 0.16,
position = position_nudge(x=0)) +
geom_point(data = filter(emmeans_full_model,
assay_temp_diff == "0"), # control temperature (background)
aes(x = trait_type:warm_cold,
y = emmean),
shape = 22,
size = 4.5,
stroke = 2,
color="white",
fill = "white",
position = position_nudge(x=0)) +
geom_errorbar(data = filter(emmeans_full_model,
assay_temp_diff == "0"), # control temperature (credible intervals)
aes(x = trait_type:warm_cold,
ymin = lower.HPD,
ymax = upper.HPD),
color="black",
size = 2,
width = 0.12,
position = position_nudge(x=0)) +
geom_point(data = filter(emmeans_full_model,
assay_temp_diff == "0"), # control temperature (point estimates)
aes(x = trait_type:warm_cold,
y = emmean),
shape = 22,
size = 3.5,
stroke = 2,
color="black",
fill = "white",
position = position_nudge(x=0)) +
geom_errorbar(data = filter(emmeans_full_model,
assay_temp_diff == "5"), # 5 degrees above control (background)
aes(x = trait_type:warm_cold,
ymin = lower.HPD,
ymax = upper.HPD),
color="white",
size = 2.75,
width = 0.16,
position = position_nudge(x=0.25)) +
geom_point(data = filter(emmeans_full_model,
assay_temp_diff == "5"), # 5 degrees above control (background)
aes(x = trait_type:warm_cold,
y = emmean),
shape = 23,
size = 4.5,
stroke = 2,
color="white",
fill = "white",
position = position_nudge(x=0.25)) +
geom_errorbar(data = filter(emmeans_full_model,
assay_temp_diff == "5"), # 5 degrees above control (credible intervals)
aes(x = trait_type:warm_cold,
ymin = lower.HPD,
ymax = upper.HPD),
color="#E80756",
size = 2,
width = 0.12,
position = position_nudge(x=0.25)) +
geom_point(data = filter(emmeans_full_model,
assay_temp_diff == "5"), # 5 degrees above control (point estimates)
aes(x = trait_type:warm_cold,
y = emmean),
shape = 23,
size = 3.5,
stroke = 2,
color="#E80756",
fill = "white",
position = position_nudge(x=0.25)) +
geom_text(data = sample_sizes, # Sample size annotations
aes(x = trait_type:warm_cold,
y = 3,
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1, size = 5, show.legend = FALSE) +
theme_bw() + # Customize the plot
labs(y = "lnCVR", x = "") +
scale_size_continuous(range = c(2, 8))+
scale_fill_manual(values = c("#06B4BA", "#E80756", "#06B4BA", "#E80756", "#06B4BA", "#E80756"))+
scale_color_manual(values = c("Cold" = "#06B4BA", "Warm" = "#E80756")) + # Text colour
theme(text = element_text(size = 20, color = "black"),
legend.title = ggplot2::element_text(size = 16),
legend.text = ggplot2::element_text(size = 14),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5)) +
guides(fill = "none", size = "none")+
coord_flip() +
ylim(-3, 3)
ggsave(file = "fig/lnCVR_all_moderators_marginal_means.png", width = 12, height = 8, dpi = 500)
Overall trend with assay temperature
# Generate predictions for all assay temperatures
emmeans_full_model_assay <- as.data.frame(emmeans(
lnCVR_model_all_moderators,
specs = ~ assay_temp_diff * select_temp_diff * gen_selection * gen_common_garden | trait_type * warm_cold,
at = list(assay_temp_diff = seq(min(data$assay_temp_diff), max(data$assay_temp_diff), by = 0.5) # Re-acclimated to control, or 5 degrees above/below the contol
)))
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_assay_temp_diff = min(assay_temp_diff),
max_assay_temp_diff = max(assay_temp_diff)
)
# Rename variable levels
emmeans_full_model_assay$trait_type <- factor(emmeans_full_model_assay$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_full_model_assay$warm_cold <- factor(emmeans_full_model_assay$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_full_model_assay <- emmeans_full_model_assay %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
assay_temp_diff >= min_assay_temp_diff,
assay_temp_diff <= max_assay_temp_diff
)
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_vline(xintercept = 0, # Vertical line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = assay_temp_diff,
y = lnCVR,
size = 1/sqrt(var_lnCVR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_full_model_assay, # Shaded area for credible intervals
aes(x = assay_temp_diff,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_full_model_assay, # Predicted regression line
aes(y = emmean,
x = assay_temp_diff,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -2, -2.75), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Assay temperature difference", y = "lnCVR",
col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 25, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 22)) +
guides(fill = "none", size = "none")+
coord_cartesian(ylim = c(-3, 3))
ggsave(file = "fig/lnCVR_all_moderators_assay_temp_diff.png", width = 12, height = 10, dpi = 500)
Changes in trait variance (lnVR)
Overall model
Model specification
# Model specification
formula <- bf(lnVR ~ trait_type -1 + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnVR)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnVR_model_overall <- brm(formula,
family = gaussian(),
data = data,
data2 = list(phylo_matrix = phylo_matrix,
VCV_lnVR = VCV_lnVR),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnVR_model_overall, file = "RData/lnVR_model_overall.rds")
Model output
# Load model
lnVR_model_overall <- readRDS("RData/lnVR_model_overall.rds")
# Display model output
summary(lnVR_model_overall)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnVR ~ trait_type - 1 + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnVR)
## Data: data (Number of observations: 476)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 88)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.09 0.06 0.00 0.22 1.01 562 1457
##
## ~obs (Number of levels: 476)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.69 0.03 0.62 0.76 1.00 2391 4360
##
## ~phylogeny (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.09 0.07 0.00 0.27 1.00 1900 2946
##
## ~ref (Number of levels: 44)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.07 0.06 0.00
## sd(trait_typefecundity) 0.07 0.06 0.00
## sd(trait_typesurvival) 0.22 0.13 0.01
## cor(trait_typebody_size,trait_typefecundity) 0.02 0.50 -0.87
## cor(trait_typebody_size,trait_typesurvival) -0.02 0.50 -0.88
## cor(trait_typefecundity,trait_typesurvival) -0.03 0.49 -0.87
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.21 1.00 1753 3140
## sd(trait_typefecundity) 0.22 1.00 1755 2773
## sd(trait_typesurvival) 0.50 1.00 958 2144
## cor(trait_typebody_size,trait_typefecundity) 0.88 1.00 4376 5091
## cor(trait_typebody_size,trait_typesurvival) 0.88 1.00 2072 3778
## cor(trait_typefecundity,trait_typesurvival) 0.86 1.00 2450 4728
##
## ~species (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.06 0.05 0.00 0.18 1.00 2486 3626
##
## Regression Coefficients:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## trait_typebody_size 0.03 0.10 -0.17 0.24 1.00 2172 2851
## trait_typefecundity -0.06 0.10 -0.25 0.14 1.00 2427 3216
## trait_typesurvival 0.11 0.13 -0.14 0.37 1.00 2994 3681
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnVR_model_overall, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(Parameter = gsub("trait_type([a-z_]+)", "\\1", Parameter)) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size | 0.030 | -0.169 | 0.243 |
| fecundity | -0.057 | -0.251 | 0.143 |
| survival | 0.110 | -0.137 | 0.366 |
Heterogeneity analysis
# Extracting the posterior distribution from the model
posterior <- posterior_samples(lnVR_model_overall)
# Calculate measurement error variance
sigma2_v <- sum(1/diag(VCV_lnVR)) * (length(diag(VCV_lnVR))- 1)/(sum(1/diag(VCV_lnVR))^2 - sum((1/diag(VCV_lnVR))^2))
# Calculate the total variance (including the measurement error variance)
var_total <- posterior$sd_experiment_ID__Intercept +
posterior$sd_obs__Intercept +
posterior$sd_phylogeny__Intercept +
posterior$sd_ref__trait_typebody_size +
posterior$sd_ref__trait_typefecundity +
posterior$sd_ref__trait_typesurvival +
posterior$sd_species__Intercept +
sigma2_v
# Calculate heterogeneity (I2)
I2 <- list(
I2_total = (var_total - sigma2_v) / var_total, # Total heterogeneity
I2_study = (posterior$sd_ref__trait_typebody_size + # Heterogeneity explained by study differences
posterior$sd_ref__trait_typefecundity +
posterior$sd_ref__trait_typesurvival) / var_total,
I2_exp = posterior$sd_experiment_ID__Intercept / var_total, # Heterogeneity explained by experiment differences
I2_sp = posterior$sd_species__Intercept / var_total, # Heterogeneity explained by species differences
I2_phylo = posterior$sd_phylogeny__Intercept / var_total, # Heterogeneity explained by phylogenetic relatedness
I2_obs = posterior$sd_obs__Intercept / var_total # Heterogeneity explained by residual variation
)
# Calculate mean and credible intervals
summary_table <- t(sapply(I2, function(x) {
c(Mean = 100*round(mean(x),4),
` ` = 100* round(quantile(x, 0.025),4),
` ` = 100*round(quantile(x, 0.975),4))
}))
# Customise table
summary_table <- summary_table %>%
as.data.frame() %>%
rownames_to_column("Variable") %>%
mutate(
Variable = recode(Variable,
"I2_total" = "Total",
"I2_study" = "Study",
"I2_exp" = "Experiment",
"I2_sp" = "Species",
"I2_phylo" = "Phylogeny",
"I2_obs" = "Residual")
) %>%
rename(
lower_HPD = ` .2.5%`,
upper_HPD = ` .97.5%`
) %>%
mutate(
Variable = paste0("I<sup>2</sup><sub>", Variable) # Create a new column with formatted I² and subscripts
)
# Display table
kable(summary_table, "html", escape = FALSE,
col.names = c("Heterogeneity (%)", "Mean", "Lower HPD", "Upper HPD"),
align = c('l', 'r', 'r', 'r')) %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, width = "200px", bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Heterogeneity (%) | Mean | Lower HPD | Upper HPD |
|---|---|---|---|
| I2Total | 93.16 | 91.17 | 94.79 |
| I2Study | 25.53 | 9.36 | 41.12 |
| I2Experiment | 6.51 | 0.33 | 14.52 |
| I2Species | 4.35 | 0.18 | 11.94 |
| I2Phylogeny | 6.57 | 0.27 | 17.79 |
| I2Residual | 50.19 | 37.83 | 64.94 |
Data visualisation
# Generate predictions
emmeans_overall <- as.data.frame(emmeans(lnVR_model_overall, ~ trait_type))
emmeans_overall$trait_type <- factor(emmeans_overall$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75,
lwd=1) +
geom_quasirandom(data = data, # Plot effect sizes, scaled by precision
aes(x = trait_type,
y = lnVR,
size = 1/sqrt(var_lnVR)),
fill = "gray75",
shape = 21,
width = 0.25,
alpha = 0.2) +
geom_errorbar(data = emmeans_overall, # Plot the point estimates in white for background
aes(ymin = lower.HPD,
ymax = upper.HPD,
x = trait_type),
size = 2,
width = 0.085,
color = "white") +
geom_point(data = emmeans_overall, # Plot the credible intervals in white for background
aes(x = trait_type,
y = emmean),
size = 4.5,
stroke = 1.5,
shape = 21,
color="white",
fill = "white") +
geom_errorbar(data = emmeans_overall, # Plot the point estimates
aes(x = trait_type,
ymin = lower.HPD,
ymax = upper.HPD),
size = 1.5,
width = 0.075,
color = "black") +
geom_point(data = emmeans_overall, # Plot the credible intervals
aes(x = trait_type,
y = emmean),
size = 4,
shape = 21,
stroke = 1.5,
color="black",
fill = "white") +
geom_text(data = sample_sizes_traits, # Sample size annotations
aes(x = trait_type,
y = 3.5,
label = paste0("k = ", estimates, " (", studies, ")")),
hjust = 1, size = 5, show.legend = FALSE) +
theme_bw() + # Customize the plot
labs(y = "lnVR", x = "") +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 20, color = "black"),
legend.title = ggplot2::element_text(size = 16),
legend.text = ggplot2::element_text(size = 14),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5)) +
guides(color = "none", size = "none")+
coord_flip() +
ylim(-3.5, 3.5)
# Save figure
ggsave(file = "fig/lnVR_overall.png", width = 12, height = 7, dpi = 500)
Temperature regime
Model specification
# Model specification
formula <- bf(lnVR ~ trait_type:warm_cold -1 + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnVR)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnVR_model_warm_cold <- brm(formula,
family = gaussian(),
data = data,
data2 = list(phylo_matrix = phylo_matrix,
VCV_lnVR = VCV_lnVR),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnVR_model_warm_cold, file = "RData/lnVR_model_warm_cold.rds")
Model output
# Load model
lnVR_model_warm_cold <- readRDS("RData/lnVR_model_warm_cold.rds")
# Display model output
summary(lnVR_model_warm_cold)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnVR ~ trait_type:warm_cold - 1 + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnVR)
## Data: data (Number of observations: 476)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 88)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.10 0.06 0.01 0.22 1.01 699 2066
##
## ~obs (Number of levels: 476)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.69 0.03 0.63 0.76 1.00 2392 4792
##
## ~phylogeny (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.09 0.07 0.00 0.27 1.00 2421 3746
##
## ~ref (Number of levels: 44)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.07 0.06 0.00
## sd(trait_typefecundity) 0.08 0.06 0.00
## sd(trait_typesurvival) 0.23 0.14 0.01
## cor(trait_typebody_size,trait_typefecundity) 0.02 0.50 -0.87
## cor(trait_typebody_size,trait_typesurvival) -0.03 0.49 -0.88
## cor(trait_typefecundity,trait_typesurvival) -0.03 0.49 -0.87
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.21 1.00 2325 4047
## sd(trait_typefecundity) 0.22 1.00 2191 3451
## sd(trait_typesurvival) 0.52 1.00 1452 2816
## cor(trait_typebody_size,trait_typefecundity) 0.88 1.00 4568 5550
## cor(trait_typebody_size,trait_typesurvival) 0.85 1.00 2186 4167
## cor(trait_typefecundity,trait_typesurvival) 0.86 1.00 2662 5481
##
## ~species (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.06 0.05 0.00 0.17 1.00 2942 5016
##
## Regression Coefficients:
## Estimate Est.Error l-95% CI u-95% CI Rhat
## trait_typebody_size:warm_coldcold -0.01 0.14 -0.28 0.26 1.00
## trait_typefecundity:warm_coldcold -0.17 0.22 -0.59 0.27 1.00
## trait_typesurvival:warm_coldcold -0.01 0.24 -0.48 0.46 1.00
## trait_typebody_size:warm_coldwarm 0.03 0.11 -0.17 0.25 1.00
## trait_typefecundity:warm_coldwarm -0.04 0.10 -0.24 0.16 1.00
## trait_typesurvival:warm_coldwarm 0.13 0.14 -0.13 0.41 1.00
## Bulk_ESS Tail_ESS
## trait_typebody_size:warm_coldcold 2592 3441
## trait_typefecundity:warm_coldcold 2897 4305
## trait_typesurvival:warm_coldcold 3848 4863
## trait_typebody_size:warm_coldwarm 2531 3095
## trait_typefecundity:warm_coldwarm 2686 3341
## trait_typesurvival:warm_coldwarm 3777 3791
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnVR_model_warm_cold, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(
# Simplify `trait_type` and `warm_cold` interactions
Parameter = gsub("trait_type([a-z_]+):warm_cold(cold|warm)", "\\1(\\2)", Parameter)
) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~ round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size(cold) | -0.010 | -0.280 | 0.264 |
| fecundity(cold) | -0.169 | -0.595 | 0.274 |
| survival(cold) | -0.006 | -0.485 | 0.458 |
| body_size(warm) | 0.034 | -0.173 | 0.255 |
| fecundity(warm) | -0.045 | -0.240 | 0.158 |
| survival(warm) | 0.132 | -0.133 | 0.407 |
Contrasts
# Generate predictions
emms <- emmeans(lnVR_model_warm_cold, specs = ~warm_cold | trait_type)
# Generate contrasts
contrast(emms, method = "pairwise")
## trait_type = body_size:
## contrast estimate lower.HPD upper.HPD
## cold - warm -0.0441 -0.296 0.214
##
## trait_type = fecundity:
## contrast estimate lower.HPD upper.HPD
## cold - warm -0.1252 -0.555 0.306
##
## trait_type = survival:
## contrast estimate lower.HPD upper.HPD
## cold - warm -0.1366 -0.621 0.347
##
## Point estimate displayed: median
## HPD interval probability: 0.95
Data visualisation
# Generate predictions
emmeans_warm_cold <- as.data.frame(emmeans(lnVR_model_warm_cold,
specs = ~ warm_cold | trait_type))
emmeans_warm_cold$trait_type <- factor(emmeans_warm_cold$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_warm_cold$warm_cold <- factor(emmeans_warm_cold$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75,
lwd=1) +
geom_quasirandom(data = data, # Plot effect sizes, scaled by precision
aes(x = trait_type:warm_cold,
y = lnVR,
fill = trait_type:warm_cold,
size = 1/sqrt(var_lnVR)),
color = "black",
shape = 21,
width = 0.25,
alpha = 0.3) +
geom_errorbar(data = emmeans_warm_cold, # Plot the point estimates in white for background
aes(x = trait_type:warm_cold,
ymin = lower.HPD,
ymax = upper.HPD),
color="white",
size = 2.5,
width = 0.17) +
geom_point(data = emmeans_warm_cold, # Plot the credible intervals in white for background
aes(x = trait_type:warm_cold,
y = emmean),
shape = 21,
size = 4,
stroke = 2,
color="white",
fill = "white") +
geom_errorbar(data = emmeans_warm_cold, # Plot the point estimates
aes(x = trait_type:warm_cold,
ymin = lower.HPD,
ymax = upper.HPD),
color="black",
size = 2,
width = 0.15) +
geom_point(data = emmeans_warm_cold, # Plot the credible intervals
aes(x = trait_type:warm_cold,
y = emmean),
shape = 21,
size = 3.5,
stroke = 2,
color="black",
fill = "white") +
geom_text(data = sample_sizes, # Sample size annotations
aes(x = trait_type:warm_cold,
y = 3.5,
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1, size = 5, show.legend = FALSE) +
theme_bw() + # Customize the plot
labs(y = "lnVR", x = "") +
scale_size_continuous(range = c(2, 8))+
# scale_fill_manual(values = c("#A5F820", "#73B706", "#9CEFFC", "#06A2BA", "#FED2ED", "#B90674"))+
scale_fill_manual(values = c("#06B4BA", "#E80756", "#06B4BA", "#E80756", "#06B4BA", "#E80756"))+
theme(text = element_text(size = 20, color = "black"),
legend.title = ggplot2::element_text(size = 16),
legend.text = ggplot2::element_text(size = 14),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5)) +
guides(fill = "none", size = "none")+
coord_flip() +
ylim(-3.5, 3.5)
ggsave(file = "fig/lnVR_warm_cold.png", width = 12, height = 7, dpi = 500)
Assay temperature difference
We calculated the difference between the assay temperature and control temperature (assay_temp_diff) to account for the fact that animals were tested to a different range of temperatures. We also predicted that effects will vary if traits are assayed at colder- or warmer-than-control conditions, depending on the temperature regime. Because the variation in assay temperature generates some nuisance heterogeneity (sensu Noble et al., 2017), the results of subsequent models were provided after controlling for assay temperature differences. In order words, the results of all moderators are conditional on assay temperature, meaning that results are interpretable as the difference between control and selected lines, when animals are tested at the control temperature.
Model specification
# Model specification
formula <- bf(lnVR ~ 0 + trait_type:warm_cold + trait_type:warm_cold:assay_temp_diff + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnVR)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnVR_model_assay_temp_diff <- brm(formula,
family = gaussian(),
data = data,
data2 = list(phylo_matrix = phylo_matrix,
VCV_lnVR = VCV_lnVR),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnVR_model_assay_temp_diff, file = "RData/lnVR_model_assay_temp_diff.rds")
Model output
# Load model
lnVR_model_assay_temp_diff <- readRDS("RData/lnVR_model_assay_temp_diff.rds")
# Display model output
summary(lnVR_model_assay_temp_diff)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnVR ~ 0 + trait_type:warm_cold + trait_type:warm_cold:assay_temp_diff + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnVR)
## Data: data (Number of observations: 476)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 88)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.09 0.06 0.01 0.21 1.01 634 1653
##
## ~obs (Number of levels: 476)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.68 0.03 0.61 0.75 1.00 2347 4647
##
## ~phylogeny (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.09 0.07 0.00 0.27 1.00 1686 3045
##
## ~ref (Number of levels: 44)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.07 0.06 0.00
## sd(trait_typefecundity) 0.08 0.06 0.00
## sd(trait_typesurvival) 0.26 0.14 0.02
## cor(trait_typebody_size,trait_typefecundity) 0.02 0.50 -0.87
## cor(trait_typebody_size,trait_typesurvival) -0.04 0.51 -0.90
## cor(trait_typefecundity,trait_typesurvival) -0.02 0.50 -0.88
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.21 1.00 1928 3376
## sd(trait_typefecundity) 0.23 1.00 1683 2508
## sd(trait_typesurvival) 0.55 1.00 1160 1944
## cor(trait_typebody_size,trait_typefecundity) 0.88 1.00 3664 4922
## cor(trait_typebody_size,trait_typesurvival) 0.88 1.00 1431 2740
## cor(trait_typefecundity,trait_typesurvival) 0.86 1.00 1714 3633
##
## ~species (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.06 0.05 0.00 0.18 1.00 2446 2781
##
## Regression Coefficients:
## Estimate Est.Error l-95% CI
## trait_typebody_size:warm_coldcold -0.02 0.14 -0.30
## trait_typefecundity:warm_coldcold -0.40 0.23 -0.85
## trait_typesurvival:warm_coldcold -0.02 0.28 -0.57
## trait_typebody_size:warm_coldwarm 0.04 0.12 -0.20
## trait_typefecundity:warm_coldwarm -0.11 0.11 -0.33
## trait_typesurvival:warm_coldwarm 0.20 0.16 -0.11
## trait_typebody_size:warm_coldcold:assay_temp_diff -0.02 0.03 -0.07
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.18 0.05 -0.27
## trait_typesurvival:warm_coldcold:assay_temp_diff -0.01 0.04 -0.10
## trait_typebody_size:warm_coldwarm:assay_temp_diff -0.00 0.02 -0.04
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.02 0.02 -0.01
## trait_typesurvival:warm_coldwarm:assay_temp_diff -0.02 0.02 -0.07
## u-95% CI Rhat Bulk_ESS
## trait_typebody_size:warm_coldcold 0.27 1.00 1835
## trait_typefecundity:warm_coldcold 0.06 1.00 2263
## trait_typesurvival:warm_coldcold 0.52 1.00 2709
## trait_typebody_size:warm_coldwarm 0.28 1.00 1844
## trait_typefecundity:warm_coldwarm 0.11 1.00 1981
## trait_typesurvival:warm_coldwarm 0.52 1.00 2465
## trait_typebody_size:warm_coldcold:assay_temp_diff 0.04 1.00 1490
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.09 1.00 2838
## trait_typesurvival:warm_coldcold:assay_temp_diff 0.07 1.00 2706
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.04 1.00 2147
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.05 1.00 1925
## trait_typesurvival:warm_coldwarm:assay_temp_diff 0.03 1.00 2693
## Tail_ESS
## trait_typebody_size:warm_coldcold 2766
## trait_typefecundity:warm_coldcold 3742
## trait_typesurvival:warm_coldcold 4244
## trait_typebody_size:warm_coldwarm 2563
## trait_typefecundity:warm_coldwarm 3040
## trait_typesurvival:warm_coldwarm 3518
## trait_typebody_size:warm_coldcold:assay_temp_diff 2883
## trait_typefecundity:warm_coldcold:assay_temp_diff 4514
## trait_typesurvival:warm_coldcold:assay_temp_diff 4385
## trait_typebody_size:warm_coldwarm:assay_temp_diff 3656
## trait_typefecundity:warm_coldwarm:assay_temp_diff 4007
## trait_typesurvival:warm_coldwarm:assay_temp_diff 4607
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnVR_model_assay_temp_diff, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(Parameter = gsub("trait_type([a-z_]+):warm_cold(cold|warm)", "\\1(\\2)",
Parameter), Parameter = gsub("trait_type([a-z_]+)\\(warm\\):assay_temp_diff",
"\\1(warm): assay_temp_diff", Parameter)) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size(cold) | -0.020 | -0.298 | 0.274 |
| fecundity(cold) | -0.401 | -0.854 | 0.062 |
| survival(cold) | -0.022 | -0.567 | 0.525 |
| body_size(warm) | 0.037 | -0.202 | 0.282 |
| fecundity(warm) | -0.110 | -0.332 | 0.106 |
| survival(warm) | 0.196 | -0.108 | 0.517 |
| body_size(cold):assay_temp_diff | -0.016 | -0.072 | 0.042 |
| fecundity(cold):assay_temp_diff | -0.176 | -0.266 | -0.085 |
| survival(cold):assay_temp_diff | -0.013 | -0.097 | 0.073 |
| body_size(warm):assay_temp_diff | -0.001 | -0.040 | 0.039 |
| fecundity(warm):assay_temp_diff | 0.020 | -0.010 | 0.050 |
| survival(warm):assay_temp_diff | -0.021 | -0.068 | 0.026 |
Data visualisation
# Generate predictions
emmeans_assay_temp_diff <- as.data.frame(emmeans(
lnVR_model_assay_temp_diff,
specs = ~ assay_temp_diff | trait_type * warm_cold,
at = list(assay_temp_diff = seq(min(data$assay_temp_diff), max(data$assay_temp_diff), by = 0.5)))
)
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_assay_temp_diff = min(assay_temp_diff),
max_assay_temp_diff = max(assay_temp_diff)
)
emmeans_assay_temp_diff$trait_type <- factor(emmeans_assay_temp_diff$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_assay_temp_diff$warm_cold <- factor(emmeans_assay_temp_diff$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_assay_temp_diff <- emmeans_assay_temp_diff %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
assay_temp_diff >= min_assay_temp_diff,
assay_temp_diff <= max_assay_temp_diff
)
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_vline(xintercept = 0, # Vertical line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = assay_temp_diff,
y = lnVR,
size = 1/sqrt(var_lnVR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_assay_temp_diff, # Shaded area for credible intervals
aes(x = assay_temp_diff,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_assay_temp_diff, # Predicted regression line
aes(y = emmean,
x = assay_temp_diff,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -2, -2.75), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Assay temperature difference", y = "lnVR",
col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 25, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 22)) +
guides(fill = "none", size = "none")+
coord_cartesian(ylim = c(-3.5, 3.5))
ggsave(file = "fig/lnVR_assay_temp_diff.png", width = 12, height = 10, dpi = 500)
Selection temperature
Model specification
# Model specification
formula <- bf(lnVR ~ 0 + trait_type:warm_cold +
trait_type:warm_cold:assay_temp_diff +
trait_type:warm_cold:select_temp_diff + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnVR)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnVR_model_sel_temp_diff <- brm(formula,
family = gaussian(),
data = data,
data2 = list(phylo_matrix = phylo_matrix,
VCV_lnVR = VCV_lnVR),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnVR_model_sel_temp_diff, file = "RData/lnVR_model_sel_temp_diff.rds")
Model output
# Load model
lnVR_model_sel_temp_diff <- readRDS("RData/lnVR_model_sel_temp_diff.rds")
# Display model output
summary(lnVR_model_sel_temp_diff)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnVR ~ 0 + trait_type:warm_cold + trait_type:warm_cold:assay_temp_diff + trait_type:warm_cold:select_temp_diff + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnVR)
## Data: data (Number of observations: 476)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 88)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.09 0.06 0.00 0.21 1.00 711 1715
##
## ~obs (Number of levels: 476)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.68 0.03 0.61 0.74 1.00 2566 4622
##
## ~phylogeny (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.10 0.08 0.00 0.28 1.00 1850 2895
##
## ~ref (Number of levels: 44)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.07 0.06 0.00
## sd(trait_typefecundity) 0.08 0.06 0.00
## sd(trait_typesurvival) 0.28 0.15 0.02
## cor(trait_typebody_size,trait_typefecundity) 0.00 0.50 -0.87
## cor(trait_typebody_size,trait_typesurvival) -0.02 0.49 -0.89
## cor(trait_typefecundity,trait_typesurvival) 0.01 0.49 -0.87
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.22 1.00 2447 4032
## sd(trait_typefecundity) 0.23 1.00 2095 3177
## sd(trait_typesurvival) 0.59 1.00 1183 2257
## cor(trait_typebody_size,trait_typefecundity) 0.88 1.00 3758 4692
## cor(trait_typebody_size,trait_typesurvival) 0.87 1.00 1905 3396
## cor(trait_typefecundity,trait_typesurvival) 0.87 1.00 2001 4219
##
## ~species (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.06 0.05 0.00 0.18 1.00 2344 3544
##
## Regression Coefficients:
## Estimate Est.Error l-95% CI
## trait_typebody_size:warm_coldcold -0.23 0.32 -0.85
## trait_typefecundity:warm_coldcold -0.14 0.65 -1.40
## trait_typesurvival:warm_coldcold 0.37 1.37 -2.36
## trait_typebody_size:warm_coldwarm 0.29 0.19 -0.07
## trait_typefecundity:warm_coldwarm 0.08 0.16 -0.23
## trait_typesurvival:warm_coldwarm 0.16 0.25 -0.33
## trait_typebody_size:warm_coldcold:assay_temp_diff -0.01 0.03 -0.07
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.19 0.05 -0.29
## trait_typesurvival:warm_coldcold:assay_temp_diff -0.02 0.05 -0.11
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.02 0.02 -0.03
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.03 0.02 -0.00
## trait_typesurvival:warm_coldwarm:assay_temp_diff -0.03 0.03 -0.08
## trait_typebody_size:warm_coldcold:select_temp_diff 0.04 0.05 -0.06
## trait_typefecundity:warm_coldcold:select_temp_diff -0.04 0.09 -0.22
## trait_typesurvival:warm_coldcold:select_temp_diff -0.05 0.18 -0.40
## trait_typebody_size:warm_coldwarm:select_temp_diff -0.06 0.03 -0.12
## trait_typefecundity:warm_coldwarm:select_temp_diff -0.05 0.03 -0.11
## trait_typesurvival:warm_coldwarm:select_temp_diff 0.01 0.04 -0.07
## u-95% CI Rhat Bulk_ESS
## trait_typebody_size:warm_coldcold 0.42 1.00 1863
## trait_typefecundity:warm_coldcold 1.13 1.00 2747
## trait_typesurvival:warm_coldcold 3.06 1.00 2812
## trait_typebody_size:warm_coldwarm 0.66 1.00 1967
## trait_typefecundity:warm_coldwarm 0.41 1.00 2485
## trait_typesurvival:warm_coldwarm 0.68 1.00 2641
## trait_typebody_size:warm_coldcold:assay_temp_diff 0.05 1.00 1565
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.09 1.00 4197
## trait_typesurvival:warm_coldcold:assay_temp_diff 0.07 1.00 2791
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.06 1.00 2351
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.07 1.00 2365
## trait_typesurvival:warm_coldwarm:assay_temp_diff 0.03 1.00 2624
## trait_typebody_size:warm_coldcold:select_temp_diff 0.13 1.00 1814
## trait_typefecundity:warm_coldcold:select_temp_diff 0.14 1.00 3165
## trait_typesurvival:warm_coldcold:select_temp_diff 0.29 1.00 2705
## trait_typebody_size:warm_coldwarm:select_temp_diff 0.00 1.00 1896
## trait_typefecundity:warm_coldwarm:select_temp_diff 0.01 1.00 2466
## trait_typesurvival:warm_coldwarm:select_temp_diff 0.08 1.00 2691
## Tail_ESS
## trait_typebody_size:warm_coldcold 3140
## trait_typefecundity:warm_coldcold 4376
## trait_typesurvival:warm_coldcold 4350
## trait_typebody_size:warm_coldwarm 3606
## trait_typefecundity:warm_coldwarm 3682
## trait_typesurvival:warm_coldwarm 4154
## trait_typebody_size:warm_coldcold:assay_temp_diff 3342
## trait_typefecundity:warm_coldcold:assay_temp_diff 5560
## trait_typesurvival:warm_coldcold:assay_temp_diff 4102
## trait_typebody_size:warm_coldwarm:assay_temp_diff 4493
## trait_typefecundity:warm_coldwarm:assay_temp_diff 3492
## trait_typesurvival:warm_coldwarm:assay_temp_diff 3933
## trait_typebody_size:warm_coldcold:select_temp_diff 2820
## trait_typefecundity:warm_coldcold:select_temp_diff 4803
## trait_typesurvival:warm_coldcold:select_temp_diff 4376
## trait_typebody_size:warm_coldwarm:select_temp_diff 3430
## trait_typefecundity:warm_coldwarm:select_temp_diff 3647
## trait_typesurvival:warm_coldwarm:select_temp_diff 3998
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnVR_model_sel_temp_diff, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(Parameter = gsub("trait_type([a-z_]+):warm_cold(cold|warm)", "\\1(\\2)",
Parameter), Parameter = gsub("trait_type([a-z_]+)\\(warm\\):select_temp_diff",
"\\1(warm): select_temp_diff", Parameter)) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size(cold) | -0.229 | -0.855 | 0.415 |
| fecundity(cold) | -0.137 | -1.404 | 1.133 |
| survival(cold) | 0.365 | -2.362 | 3.059 |
| body_size(warm) | 0.285 | -0.070 | 0.658 |
| fecundity(warm) | 0.076 | -0.233 | 0.406 |
| survival(warm) | 0.158 | -0.327 | 0.680 |
| body_size(cold):assay_temp_diff | -0.007 | -0.068 | 0.052 |
| fecundity(cold):assay_temp_diff | -0.185 | -0.285 | -0.087 |
| survival(cold):assay_temp_diff | -0.018 | -0.106 | 0.071 |
| body_size(warm):assay_temp_diff | 0.017 | -0.027 | 0.059 |
| fecundity(warm):assay_temp_diff | 0.032 | -0.003 | 0.066 |
| survival(warm):assay_temp_diff | -0.025 | -0.077 | 0.026 |
| body_size(cold):select_temp_diff | 0.035 | -0.062 | 0.130 |
| fecundity(cold):select_temp_diff | -0.040 | -0.216 | 0.137 |
| survival(cold):select_temp_diff | -0.052 | -0.403 | 0.294 |
| body_size(warm):select_temp_diff | -0.055 | -0.115 | 0.005 |
| fecundity(warm):select_temp_diff | -0.051 | -0.114 | 0.011 |
| survival(warm):select_temp_diff | 0.008 | -0.070 | 0.080 |
Data visualisation
# Generate predictions
emmeans_sel_temp_diff <- as.data.frame(emmeans(
lnVR_model_sel_temp_diff,
specs = ~ select_temp_diff | trait_type * warm_cold,
at = list(select_temp_diff = seq(min(data$select_temp_diff), max(data$select_temp_diff), by = 0.5),
assay_temp_diff = 0))
) # Conditional effects on assay_temp_diff = 0
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_select_temp_diff = min(select_temp_diff),
max_select_temp_diff = max(select_temp_diff)
)
emmeans_sel_temp_diff$trait_type <- factor(emmeans_sel_temp_diff$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_sel_temp_diff$warm_cold <- factor(emmeans_sel_temp_diff$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_sel_temp_diff <- emmeans_sel_temp_diff %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
select_temp_diff >= min_select_temp_diff,
select_temp_diff <= max_select_temp_diff
)
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = select_temp_diff,
y = lnVR,
size = 1/sqrt(var_lnVR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_sel_temp_diff, # Shaded area for credible intervals
aes(x = select_temp_diff,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_sel_temp_diff, # Predicted regression line
aes(y = emmean,
x = select_temp_diff,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -2, -2.75), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Selection temperature difference", y = "lnVR", col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 25, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 22)) +
guides(fill = "none", size = "none")+
coord_cartesian(ylim = c(-3.5, 3.5))
ggsave(file = "fig/lnVR_sel_temp_diff.png", width = 12, height = 7, dpi = 500)
Number of generations of selection
Model specification
# Model specification
formula <- bf(lnVR ~ 0 + trait_type:warm_cold +
trait_type:warm_cold:assay_temp_diff +
trait_type:warm_cold:scale(gen_selection) + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnVR)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnVR_model_gen_selection <- brm(formula,
family = gaussian(),
data = data,
data2 = list(phylo_matrix = phylo_matrix,
VCV_lnVR = VCV_lnVR),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnVR_model_gen_selection, file = "RData/lnVR_model_gen_selection.rds")
Model output
# Load model
lnVR_model_gen_selection <- readRDS("RData/lnVR_model_gen_selection.rds")
# Display model output
summary(lnVR_model_gen_selection)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnVR ~ 0 + trait_type:warm_cold + trait_type:warm_cold:assay_temp_diff + trait_type:warm_cold:scale(gen_selection) + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnVR)
## Data: data (Number of observations: 476)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 88)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.09 0.06 0.00 0.21 1.01 903 2637
##
## ~obs (Number of levels: 476)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.68 0.03 0.61 0.75 1.00 3178 4871
##
## ~phylogeny (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.09 0.07 0.00 0.27 1.00 3124 3917
##
## ~ref (Number of levels: 44)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.07 0.06 0.00
## sd(trait_typefecundity) 0.08 0.06 0.00
## sd(trait_typesurvival) 0.30 0.15 0.03
## cor(trait_typebody_size,trait_typefecundity) 0.01 0.50 -0.87
## cor(trait_typebody_size,trait_typesurvival) -0.02 0.50 -0.88
## cor(trait_typefecundity,trait_typesurvival) -0.04 0.49 -0.88
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.22 1.00 2998 4012
## sd(trait_typefecundity) 0.23 1.00 2675 4058
## sd(trait_typesurvival) 0.61 1.00 1224 1341
## cor(trait_typebody_size,trait_typefecundity) 0.88 1.00 5312 5802
## cor(trait_typebody_size,trait_typesurvival) 0.88 1.00 2069 3948
## cor(trait_typefecundity,trait_typesurvival) 0.86 1.00 2585 4515
##
## ~species (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.06 0.05 0.00 0.17 1.00 3260 4818
##
## Regression Coefficients:
## Estimate Est.Error
## trait_typebody_size:warm_coldcold -0.08 0.15
## trait_typefecundity:warm_coldcold -0.31 0.28
## trait_typesurvival:warm_coldcold 0.05 0.35
## trait_typebody_size:warm_coldwarm 0.03 0.12
## trait_typefecundity:warm_coldwarm -0.18 0.12
## trait_typesurvival:warm_coldwarm 0.21 0.20
## trait_typebody_size:warm_coldcold:assay_temp_diff -0.01 0.03
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.19 0.05
## trait_typesurvival:warm_coldcold:assay_temp_diff -0.02 0.05
## trait_typebody_size:warm_coldwarm:assay_temp_diff -0.00 0.02
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.03 0.02
## trait_typesurvival:warm_coldwarm:assay_temp_diff -0.02 0.02
## trait_typebody_size:warm_coldcold:scalegen_selection 0.09 0.11
## trait_typefecundity:warm_coldcold:scalegen_selection -0.13 0.16
## trait_typesurvival:warm_coldcold:scalegen_selection -0.09 0.20
## trait_typebody_size:warm_coldwarm:scalegen_selection -0.03 0.07
## trait_typefecundity:warm_coldwarm:scalegen_selection -0.18 0.12
## trait_typesurvival:warm_coldwarm:scalegen_selection 0.04 0.30
## l-95% CI u-95% CI Rhat
## trait_typebody_size:warm_coldcold -0.38 0.22 1.00
## trait_typefecundity:warm_coldcold -0.86 0.24 1.00
## trait_typesurvival:warm_coldcold -0.66 0.74 1.00
## trait_typebody_size:warm_coldwarm -0.21 0.27 1.00
## trait_typefecundity:warm_coldwarm -0.41 0.05 1.00
## trait_typesurvival:warm_coldwarm -0.17 0.60 1.00
## trait_typebody_size:warm_coldcold:assay_temp_diff -0.07 0.05 1.00
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.28 -0.09 1.00
## trait_typesurvival:warm_coldcold:assay_temp_diff -0.11 0.07 1.00
## trait_typebody_size:warm_coldwarm:assay_temp_diff -0.04 0.04 1.00
## trait_typefecundity:warm_coldwarm:assay_temp_diff -0.01 0.06 1.00
## trait_typesurvival:warm_coldwarm:assay_temp_diff -0.07 0.02 1.00
## trait_typebody_size:warm_coldcold:scalegen_selection -0.12 0.30 1.00
## trait_typefecundity:warm_coldcold:scalegen_selection -0.43 0.19 1.00
## trait_typesurvival:warm_coldcold:scalegen_selection -0.47 0.30 1.00
## trait_typebody_size:warm_coldwarm:scalegen_selection -0.17 0.10 1.00
## trait_typefecundity:warm_coldwarm:scalegen_selection -0.42 0.05 1.00
## trait_typesurvival:warm_coldwarm:scalegen_selection -0.56 0.64 1.00
## Bulk_ESS Tail_ESS
## trait_typebody_size:warm_coldcold 3963 4998
## trait_typefecundity:warm_coldcold 5269 5838
## trait_typesurvival:warm_coldcold 4825 5100
## trait_typebody_size:warm_coldwarm 3397 4617
## trait_typefecundity:warm_coldwarm 3245 4514
## trait_typesurvival:warm_coldwarm 4858 5210
## trait_typebody_size:warm_coldcold:assay_temp_diff 2958 4256
## trait_typefecundity:warm_coldcold:assay_temp_diff 5388 5860
## trait_typesurvival:warm_coldcold:assay_temp_diff 4795 5591
## trait_typebody_size:warm_coldwarm:assay_temp_diff 3909 5356
## trait_typefecundity:warm_coldwarm:assay_temp_diff 3454 4427
## trait_typesurvival:warm_coldwarm:assay_temp_diff 5143 6729
## trait_typebody_size:warm_coldcold:scalegen_selection 3563 4687
## trait_typefecundity:warm_coldcold:scalegen_selection 5708 5582
## trait_typesurvival:warm_coldcold:scalegen_selection 4861 5581
## trait_typebody_size:warm_coldwarm:scalegen_selection 4280 5411
## trait_typefecundity:warm_coldwarm:scalegen_selection 3373 4641
## trait_typesurvival:warm_coldwarm:scalegen_selection 5155 5260
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnVR_model_gen_selection, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(
# Simplify `trait_type` and `warm_cold` interactions
Parameter = gsub("trait_type([a-z_]+):warm_cold(cold|warm)", "\\1(\\2)", Parameter),
# Adjust for assay_temp_diff
Parameter = gsub("trait_type([a-z_]+)\\((cold|warm)\\):assay_temp_diff", "\\1(\\2): assay_temp_diff", Parameter),
# Remove "scale" from gen_selection
Parameter = gsub("scalegen_selection", "gen_selection", Parameter)
) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~ round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size(cold) | -0.084 | -0.383 | 0.217 |
| fecundity(cold) | -0.307 | -0.856 | 0.238 |
| survival(cold) | 0.049 | -0.664 | 0.737 |
| body_size(warm) | 0.031 | -0.214 | 0.271 |
| fecundity(warm) | -0.182 | -0.413 | 0.051 |
| survival(warm) | 0.213 | -0.167 | 0.600 |
| body_size(cold):assay_temp_diff | -0.008 | -0.069 | 0.054 |
| fecundity(cold):assay_temp_diff | -0.188 | -0.283 | -0.089 |
| survival(cold):assay_temp_diff | -0.019 | -0.109 | 0.071 |
| body_size(warm):assay_temp_diff | -0.001 | -0.041 | 0.038 |
| fecundity(warm):assay_temp_diff | 0.026 | -0.006 | 0.058 |
| survival(warm):assay_temp_diff | -0.023 | -0.070 | 0.023 |
| body_size(cold):gen_selection | 0.088 | -0.119 | 0.303 |
| fecundity(cold):gen_selection | -0.127 | -0.427 | 0.186 |
| survival(cold):gen_selection | -0.088 | -0.474 | 0.301 |
| body_size(warm):gen_selection | -0.031 | -0.169 | 0.102 |
| fecundity(warm):gen_selection | -0.184 | -0.424 | 0.054 |
| survival(warm):gen_selection | 0.038 | -0.563 | 0.635 |
Data visualisation
# Generate predictions
emmeans_gen_selection <- as.data.frame(emmeans(
lnVR_model_gen_selection,
specs = ~ gen_selection | trait_type * warm_cold,
at = list(gen_selection = seq(min(data$gen_selection), max(data$gen_selection), by = 1),
assay_temp_diff = 0))
) # Conditional effects on assay_temp_diff = 0
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_gen_selection = min(gen_selection),
max_gen_selection = max(gen_selection)
)
emmeans_gen_selection$trait_type <- factor(emmeans_gen_selection$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_gen_selection$warm_cold <- factor(emmeans_gen_selection$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_gen_selection <- emmeans_gen_selection %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
gen_selection >= min_gen_selection,
gen_selection <= max_gen_selection
)
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = gen_selection,
y = lnVR,
size = 1/sqrt(var_lnVR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_gen_selection, # Shaded area for credible intervals
aes(x = gen_selection,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_gen_selection, # Predicted regression line
aes(y = emmean,
x = gen_selection,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -2, -2.75), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Number of generations of selection", y = "lnVR", col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 25, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 22)) +
guides(fill = "none", size = "none")+
coord_cartesian(ylim = c(-3.5, 3.5),
xlim = c(0, 150))
ggsave(file = "fig/lnVR_gen_selection.png", width = 12, height = 7, dpi = 500)
Number of generations of common garden
Model specification
# Model specification
formula <- bf(lnVR ~ 0 + trait_type:warm_cold +
trait_type:warm_cold:assay_temp_diff +
trait_type:warm_cold:scale(gen_common_garden) + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnVR)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnVR_model_gen_common_garden <- brm(formula,
family = gaussian(),
data = data,
data2 = list(phylo_matrix = phylo_matrix,
VCV_lnVR = VCV_lnVR),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnVR_model_gen_common_garden, file = "RData/lnVR_model_gen_common_garden.rds")
Model output
# Load model
lnVR_model_gen_common_garden <- readRDS("RData/lnVR_model_gen_common_garden.rds")
# Display model output
summary(lnVR_model_gen_common_garden)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnVR ~ 0 + trait_type:warm_cold + trait_type:warm_cold:assay_temp_diff + trait_type:warm_cold:scale(gen_common_garden) + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnVR)
## Data: data (Number of observations: 476)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 88)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.09 0.06 0.00 0.22 1.00 734 2267
##
## ~obs (Number of levels: 476)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.68 0.03 0.62 0.75 1.00 2503 3863
##
## ~phylogeny (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.10 0.08 0.00 0.30 1.00 1922 2831
##
## ~ref (Number of levels: 44)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.08 0.06 0.00
## sd(trait_typefecundity) 0.08 0.07 0.00
## sd(trait_typesurvival) 0.29 0.15 0.03
## cor(trait_typebody_size,trait_typefecundity) 0.01 0.50 -0.86
## cor(trait_typebody_size,trait_typesurvival) -0.04 0.50 -0.90
## cor(trait_typefecundity,trait_typesurvival) -0.03 0.49 -0.89
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.23 1.00 1856 2694
## sd(trait_typefecundity) 0.24 1.00 1538 2748
## sd(trait_typesurvival) 0.61 1.01 1068 1667
## cor(trait_typebody_size,trait_typefecundity) 0.87 1.00 3422 5458
## cor(trait_typebody_size,trait_typesurvival) 0.87 1.00 1339 2732
## cor(trait_typefecundity,trait_typesurvival) 0.85 1.00 1742 4248
##
## ~species (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.06 0.05 0.00 0.18 1.00 2330 3367
##
## Regression Coefficients:
## Estimate Est.Error
## trait_typebody_size:warm_coldcold -0.01 0.15
## trait_typefecundity:warm_coldcold -0.37 0.23
## trait_typesurvival:warm_coldcold -0.03 0.30
## trait_typebody_size:warm_coldwarm 0.02 0.12
## trait_typefecundity:warm_coldwarm -0.11 0.11
## trait_typesurvival:warm_coldwarm 0.20 0.17
## trait_typebody_size:warm_coldcold:assay_temp_diff -0.02 0.03
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.18 0.05
## trait_typesurvival:warm_coldcold:assay_temp_diff -0.01 0.05
## trait_typebody_size:warm_coldwarm:assay_temp_diff -0.00 0.02
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.02 0.02
## trait_typesurvival:warm_coldwarm:assay_temp_diff -0.02 0.02
## trait_typebody_size:warm_coldcold:scalegen_common_garden -0.06 0.58
## trait_typefecundity:warm_coldcold:scalegen_common_garden 0.95 0.95
## trait_typesurvival:warm_coldcold:scalegen_common_garden 0.06 0.82
## trait_typebody_size:warm_coldwarm:scalegen_common_garden -0.28 0.45
## trait_typefecundity:warm_coldwarm:scalegen_common_garden -0.00 0.04
## trait_typesurvival:warm_coldwarm:scalegen_common_garden 0.08 0.68
## l-95% CI u-95% CI Rhat
## trait_typebody_size:warm_coldcold -0.30 0.29 1.00
## trait_typefecundity:warm_coldcold -0.83 0.09 1.00
## trait_typesurvival:warm_coldcold -0.60 0.57 1.00
## trait_typebody_size:warm_coldwarm -0.21 0.27 1.00
## trait_typefecundity:warm_coldwarm -0.33 0.12 1.00
## trait_typesurvival:warm_coldwarm -0.12 0.54 1.00
## trait_typebody_size:warm_coldcold:assay_temp_diff -0.07 0.04 1.00
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.27 -0.09 1.00
## trait_typesurvival:warm_coldcold:assay_temp_diff -0.10 0.08 1.00
## trait_typebody_size:warm_coldwarm:assay_temp_diff -0.04 0.04 1.00
## trait_typefecundity:warm_coldwarm:assay_temp_diff -0.01 0.05 1.00
## trait_typesurvival:warm_coldwarm:assay_temp_diff -0.07 0.03 1.00
## trait_typebody_size:warm_coldcold:scalegen_common_garden -1.18 1.07 1.00
## trait_typefecundity:warm_coldcold:scalegen_common_garden -0.92 2.80 1.00
## trait_typesurvival:warm_coldcold:scalegen_common_garden -1.57 1.70 1.00
## trait_typebody_size:warm_coldwarm:scalegen_common_garden -1.15 0.59 1.00
## trait_typefecundity:warm_coldwarm:scalegen_common_garden -0.07 0.07 1.00
## trait_typesurvival:warm_coldwarm:scalegen_common_garden -1.25 1.42 1.00
## Bulk_ESS Tail_ESS
## trait_typebody_size:warm_coldcold 1923 2788
## trait_typefecundity:warm_coldcold 2798 4039
## trait_typesurvival:warm_coldcold 2314 3598
## trait_typebody_size:warm_coldwarm 1856 2840
## trait_typefecundity:warm_coldwarm 1929 3018
## trait_typesurvival:warm_coldwarm 2686 2899
## trait_typebody_size:warm_coldcold:assay_temp_diff 1669 2727
## trait_typefecundity:warm_coldcold:assay_temp_diff 3397 4792
## trait_typesurvival:warm_coldcold:assay_temp_diff 2894 4083
## trait_typebody_size:warm_coldwarm:assay_temp_diff 2413 3406
## trait_typefecundity:warm_coldwarm:assay_temp_diff 2015 3520
## trait_typesurvival:warm_coldwarm:assay_temp_diff 3059 4817
## trait_typebody_size:warm_coldcold:scalegen_common_garden 2614 4742
## trait_typefecundity:warm_coldcold:scalegen_common_garden 4186 5157
## trait_typesurvival:warm_coldcold:scalegen_common_garden 3684 4803
## trait_typebody_size:warm_coldwarm:scalegen_common_garden 2435 4001
## trait_typefecundity:warm_coldwarm:scalegen_common_garden 2949 4546
## trait_typesurvival:warm_coldwarm:scalegen_common_garden 3369 4401
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnVR_model_gen_common_garden, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(
# Simplify `trait_type` and `warm_cold` interactions
Parameter = gsub("trait_type([a-z_]+):warm_cold(cold|warm)", "\\1(\\2)", Parameter),
# Adjust for assay_temp_diff
Parameter = gsub("trait_type([a-z_]+)\\((cold|warm)\\):assay_temp_diff", "\\1(\\2): assay_temp_diff", Parameter),
# Remove "scale" from gen_selection
Parameter = gsub("scalegen_common_garden", "gen_common_garden", Parameter)
) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~ round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size(cold) | -0.014 | -0.295 | 0.286 |
| fecundity(cold) | -0.371 | -0.833 | 0.087 |
| survival(cold) | -0.026 | -0.598 | 0.566 |
| body_size(warm) | 0.025 | -0.215 | 0.274 |
| fecundity(warm) | -0.108 | -0.334 | 0.121 |
| survival(warm) | 0.204 | -0.119 | 0.545 |
| body_size(cold):assay_temp_diff | -0.015 | -0.073 | 0.041 |
| fecundity(cold):assay_temp_diff | -0.181 | -0.273 | -0.087 |
| survival(cold):assay_temp_diff | -0.013 | -0.102 | 0.077 |
| body_size(warm):assay_temp_diff | -0.002 | -0.042 | 0.036 |
| fecundity(warm):assay_temp_diff | 0.020 | -0.011 | 0.051 |
| survival(warm):assay_temp_diff | -0.021 | -0.069 | 0.027 |
| body_size(cold):gen_common_garden | -0.064 | -1.182 | 1.067 |
| fecundity(cold):gen_common_garden | 0.954 | -0.918 | 2.804 |
| survival(cold):gen_common_garden | 0.063 | -1.566 | 1.701 |
| body_size(warm):gen_common_garden | -0.285 | -1.152 | 0.585 |
| fecundity(warm):gen_common_garden | -0.001 | -0.073 | 0.069 |
| survival(warm):gen_common_garden | 0.083 | -1.248 | 1.418 |
Data visualisation
# Generate predictions
emmeans_gen_common_garden <- as.data.frame(emmeans(
lnVR_model_gen_common_garden,
specs = ~ gen_common_garden | trait_type * warm_cold,
at = list(gen_common_garden = seq(min(data$gen_common_garden), max(data$gen_common_garden), by = 1),
assay_temp_diff = 0))
) # Conditional effects on assay_temp_diff = 0
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_gen_common_garden = min(gen_common_garden),
max_gen_common_garden = max(gen_common_garden)
)
emmeans_gen_common_garden$trait_type <- factor(emmeans_gen_common_garden$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_gen_common_garden$warm_cold <- factor(emmeans_gen_common_garden$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_gen_common_garden <- emmeans_gen_common_garden %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
gen_common_garden >= min_gen_common_garden,
gen_common_garden <= max_gen_common_garden
)
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = gen_common_garden,
y = lnVR,
size = 1/sqrt(var_lnVR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_gen_common_garden, # Shaded area for credible intervals
aes(x = gen_common_garden,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_gen_common_garden, # Predicted regression line
aes(y = emmean,
x = gen_common_garden,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -2, -2.75), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Number of generations of common garden", y = "lnVR", col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 25, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 22)) +
guides(fill = "none", size = "none")+
coord_cartesian(ylim = c(-3.5, 3.5),
xlim = c(0, 12))
ggsave(file = "fig/lnVR_gen_common_garden.png", width = 12, height = 7, dpi = 500)
Population size
Model specification
# Model specification
formula <- bf(lnVR ~ 0 + trait_type:warm_cold +
trait_type:warm_cold:assay_temp_diff +
trait_type:warm_cold:scale(pop_size) + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnVR)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnVR_model_pop_size <- brm(formula,
family = gaussian(),
data = data,
data2 = list(phylo_matrix = phylo_matrix,
VCV_lnVR = VCV_lnVR),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnVR_model_pop_size, file = "RData/lnVR_model_pop_size.rds")
Model output
# Load model
lnVR_model_pop_size <- readRDS("RData/lnVR_model_pop_size.rds")
# Display model output
summary(lnVR_model_pop_size)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnVR ~ 0 + trait_type:warm_cold + trait_type:warm_cold:assay_temp_diff + trait_type:warm_cold:scale(pop_size) + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnVR)
## Data: data (Number of observations: 426)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 79)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.07 0.05 0.00 0.18 1.00 952 2315
##
## ~obs (Number of levels: 426)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.36 0.03 0.31 0.42 1.00 2535 4161
##
## ~phylogeny (Number of levels: 22)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.08 0.06 0.00 0.24 1.00 2431 3797
##
## ~ref (Number of levels: 41)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.07 0.06 0.00
## sd(trait_typefecundity) 0.10 0.07 0.00
## sd(trait_typesurvival) 0.25 0.12 0.03
## cor(trait_typebody_size,trait_typefecundity) 0.05 0.50 -0.87
## cor(trait_typebody_size,trait_typesurvival) -0.10 0.50 -0.90
## cor(trait_typefecundity,trait_typesurvival) 0.02 0.49 -0.87
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.21 1.00 1826 2849
## sd(trait_typefecundity) 0.25 1.00 1356 2414
## sd(trait_typesurvival) 0.50 1.00 1478 1451
## cor(trait_typebody_size,trait_typefecundity) 0.89 1.00 2645 4060
## cor(trait_typebody_size,trait_typesurvival) 0.84 1.00 1484 2835
## cor(trait_typefecundity,trait_typesurvival) 0.87 1.00 1913 3967
##
## ~species (Number of levels: 22)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.06 0.05 0.00 0.17 1.00 2499 3232
##
## Regression Coefficients:
## Estimate Est.Error l-95% CI
## trait_typebody_size:warm_coldcold 0.04 0.12 -0.19
## trait_typefecundity:warm_coldcold -0.37 0.17 -0.70
## trait_typesurvival:warm_coldcold -0.09 0.21 -0.51
## trait_typebody_size:warm_coldwarm -0.01 0.10 -0.20
## trait_typefecundity:warm_coldwarm -0.06 0.09 -0.24
## trait_typesurvival:warm_coldwarm 0.03 0.13 -0.21
## trait_typebody_size:warm_coldcold:assay_temp_diff -0.00 0.02 -0.04
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.17 0.03 -0.24
## trait_typesurvival:warm_coldcold:assay_temp_diff -0.02 0.03 -0.08
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.00 0.01 -0.02
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.00 0.01 -0.02
## trait_typesurvival:warm_coldwarm:assay_temp_diff 0.00 0.02 -0.03
## trait_typebody_size:warm_coldcold:scalepop_size -0.11 0.08 -0.27
## trait_typefecundity:warm_coldcold:scalepop_size 0.04 0.15 -0.24
## trait_typesurvival:warm_coldcold:scalepop_size 0.06 0.21 -0.35
## trait_typebody_size:warm_coldwarm:scalepop_size -0.02 0.05 -0.11
## trait_typefecundity:warm_coldwarm:scalepop_size -0.06 0.06 -0.19
## trait_typesurvival:warm_coldwarm:scalepop_size -0.03 0.10 -0.23
## u-95% CI Rhat Bulk_ESS
## trait_typebody_size:warm_coldcold 0.27 1.00 2129
## trait_typefecundity:warm_coldcold -0.03 1.00 3984
## trait_typesurvival:warm_coldcold 0.33 1.00 3648
## trait_typebody_size:warm_coldwarm 0.19 1.00 2557
## trait_typefecundity:warm_coldwarm 0.12 1.00 2826
## trait_typesurvival:warm_coldwarm 0.30 1.00 2914
## trait_typebody_size:warm_coldcold:assay_temp_diff 0.04 1.00 2644
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.10 1.00 4468
## trait_typesurvival:warm_coldcold:assay_temp_diff 0.04 1.00 4081
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.03 1.00 3677
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.03 1.00 3902
## trait_typesurvival:warm_coldwarm:assay_temp_diff 0.04 1.00 4262
## trait_typebody_size:warm_coldcold:scalepop_size 0.04 1.00 2382
## trait_typefecundity:warm_coldcold:scalepop_size 0.33 1.00 3577
## trait_typesurvival:warm_coldcold:scalepop_size 0.46 1.00 4371
## trait_typebody_size:warm_coldwarm:scalepop_size 0.08 1.00 3030
## trait_typefecundity:warm_coldwarm:scalepop_size 0.06 1.00 3293
## trait_typesurvival:warm_coldwarm:scalepop_size 0.16 1.00 3812
## Tail_ESS
## trait_typebody_size:warm_coldcold 3634
## trait_typefecundity:warm_coldcold 4760
## trait_typesurvival:warm_coldcold 4534
## trait_typebody_size:warm_coldwarm 3089
## trait_typefecundity:warm_coldwarm 3984
## trait_typesurvival:warm_coldwarm 3027
## trait_typebody_size:warm_coldcold:assay_temp_diff 4285
## trait_typefecundity:warm_coldcold:assay_temp_diff 5294
## trait_typesurvival:warm_coldcold:assay_temp_diff 4976
## trait_typebody_size:warm_coldwarm:assay_temp_diff 4957
## trait_typefecundity:warm_coldwarm:assay_temp_diff 5487
## trait_typesurvival:warm_coldwarm:assay_temp_diff 5544
## trait_typebody_size:warm_coldcold:scalepop_size 4168
## trait_typefecundity:warm_coldcold:scalepop_size 4757
## trait_typesurvival:warm_coldcold:scalepop_size 4857
## trait_typebody_size:warm_coldwarm:scalepop_size 4325
## trait_typefecundity:warm_coldwarm:scalepop_size 4563
## trait_typesurvival:warm_coldwarm:scalepop_size 4889
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnVR_model_pop_size, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(
# Simplify `trait_type` and `warm_cold` interactions
Parameter = gsub("trait_type([a-z_]+):warm_cold(cold|warm)", "\\1(\\2)", Parameter),
# Adjust for assay_temp_diff
Parameter = gsub("trait_type([a-z_]+)\\((cold|warm)\\):assay_temp_diff", "\\1(\\2): assay_temp_diff", Parameter),
# Remove "scale" from pop_size
Parameter = gsub("scalepop_size", "pop_size", Parameter)
) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~ round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size(cold) | 0.037 | -0.193 | 0.274 |
| fecundity(cold) | -0.371 | -0.702 | -0.030 |
| survival(cold) | -0.093 | -0.513 | 0.328 |
| body_size(warm) | -0.005 | -0.197 | 0.187 |
| fecundity(warm) | -0.062 | -0.239 | 0.119 |
| survival(warm) | 0.031 | -0.214 | 0.295 |
| body_size(cold):assay_temp_diff | 0.000 | -0.040 | 0.039 |
| fecundity(cold):assay_temp_diff | -0.171 | -0.240 | -0.103 |
| survival(cold):assay_temp_diff | -0.019 | -0.080 | 0.042 |
| body_size(warm):assay_temp_diff | 0.005 | -0.024 | 0.033 |
| fecundity(warm):assay_temp_diff | 0.004 | -0.018 | 0.027 |
| survival(warm):assay_temp_diff | 0.003 | -0.033 | 0.038 |
| body_size(cold):pop_size | -0.113 | -0.268 | 0.041 |
| fecundity(cold):pop_size | 0.043 | -0.244 | 0.329 |
| survival(cold):pop_size | 0.056 | -0.350 | 0.462 |
| body_size(warm):pop_size | -0.016 | -0.115 | 0.083 |
| fecundity(warm):pop_size | -0.060 | -0.187 | 0.060 |
| survival(warm):pop_size | -0.030 | -0.230 | 0.160 |
Data visualisation
# Generate predictions
emmeans_pop_size <- as.data.frame(emmeans(
lnVR_model_pop_size,
specs = ~ pop_size | trait_type * warm_cold,
at = list(pop_size = seq(min(data$pop_size, na.rm=T), max(data$pop_size, na.rm=T), by = 50),
assay_temp_diff = 0))
) # Conditional effects on assay_temp_diff = 0
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_pop_size = min(pop_size, na.rm=T),
max_pop_size = max(pop_size, na.rm=T)
)
emmeans_pop_size$trait_type <- factor(emmeans_pop_size$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_pop_size$warm_cold <- factor(emmeans_pop_size$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_pop_size <- emmeans_pop_size %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
pop_size >= min_pop_size,
pop_size <= max_pop_size
)
# Calculate sample sizes and study counts for population size
sample_sizes_pop_size <- data %>%
filter(is.na(pop_size)==FALSE) %>%
group_by(trait_type, warm_cold) %>%
summarise(
estimates = n(),
studies = n_distinct(ref)
)
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = pop_size,
y = lnVR,
size = 1/sqrt(var_lnVR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_pop_size, # Shaded area for credible intervals
aes(x = pop_size,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_pop_size, # Predicted regression line
aes(y = emmean,
x = pop_size,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes_pop_size,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -2, -2.75), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Population size", y = "lnVR", col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 25, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 22)) +
guides(fill = "none", size = "none")+
ylim(-3.5, 3.5)
ggsave(file = "fig/lnVR_pop_size.png", width = 12, height = 7, dpi = 500)
All moderators
Model specification
# Model specification
formula <- bf(lnVR ~ 0 + trait_type:warm_cold +
trait_type:warm_cold:assay_temp_diff +
trait_type:warm_cold:scale(select_temp_diff) +
trait_type:warm_cold:scale(gen_selection) +
trait_type:warm_cold:scale(gen_common_garden) + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnVR)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnVR_model_all_moderators <- brm(formula,
family = gaussian(),
data = data,
data2 = list(phylo_matrix = phylo_matrix,
VCV_lnVR = VCV_lnVR),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnVR_model_all_moderators, file = "RData/lnVR_model_all_moderators.rds")
Model output
# Load model
lnVR_model_all_moderators <- readRDS("RData/lnVR_model_all_moderators.rds")
# Display model output
summary(lnVR_model_all_moderators)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnVR ~ 0 + trait_type:warm_cold + trait_type:warm_cold:assay_temp_diff + trait_type:warm_cold:scale(select_temp_diff) + trait_type:warm_cold:scale(gen_selection) + trait_type:warm_cold:scale(gen_common_garden) + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnVR)
## Data: data (Number of observations: 476)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 88)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.10 0.06 0.01 0.23 1.01 897 2200
##
## ~obs (Number of levels: 476)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.68 0.03 0.62 0.75 1.00 3468 5446
##
## ~phylogeny (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.10 0.08 0.00 0.29 1.00 2518 3463
##
## ~ref (Number of levels: 44)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.08 0.06 0.00
## sd(trait_typefecundity) 0.08 0.07 0.00
## sd(trait_typesurvival) 0.35 0.18 0.03
## cor(trait_typebody_size,trait_typefecundity) -0.01 0.51 -0.88
## cor(trait_typebody_size,trait_typesurvival) -0.02 0.50 -0.88
## cor(trait_typefecundity,trait_typesurvival) -0.01 0.49 -0.87
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.24 1.00 2879 3802
## sd(trait_typefecundity) 0.25 1.00 2273 3743
## sd(trait_typesurvival) 0.72 1.00 1272 1684
## cor(trait_typebody_size,trait_typefecundity) 0.89 1.00 5698 5658
## cor(trait_typebody_size,trait_typesurvival) 0.87 1.00 1652 3796
## cor(trait_typefecundity,trait_typesurvival) 0.86 1.00 2195 4761
##
## ~species (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.06 0.05 0.00 0.19 1.00 3341 4704
##
## Regression Coefficients:
## Estimate Est.Error
## trait_typebody_size:warm_coldcold -0.08 0.16
## trait_typefecundity:warm_coldcold -0.28 0.30
## trait_typesurvival:warm_coldcold 0.11 0.50
## trait_typebody_size:warm_coldwarm -0.04 0.13
## trait_typefecundity:warm_coldwarm -0.23 0.13
## trait_typesurvival:warm_coldwarm 0.22 0.22
## trait_typebody_size:warm_coldcold:assay_temp_diff -0.00 0.03
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.20 0.05
## trait_typesurvival:warm_coldcold:assay_temp_diff -0.02 0.05
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.02 0.02
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.03 0.02
## trait_typesurvival:warm_coldwarm:assay_temp_diff -0.03 0.03
## trait_typebody_size:warm_coldcold:scaleselect_temp_diff 0.09 0.18
## trait_typefecundity:warm_coldcold:scaleselect_temp_diff -0.15 0.29
## trait_typesurvival:warm_coldcold:scaleselect_temp_diff -0.03 0.85
## trait_typebody_size:warm_coldwarm:scaleselect_temp_diff -0.18 0.10
## trait_typefecundity:warm_coldwarm:scaleselect_temp_diff -0.11 0.11
## trait_typesurvival:warm_coldwarm:scaleselect_temp_diff 0.01 0.14
## trait_typebody_size:warm_coldcold:scalegen_selection 0.07 0.12
## trait_typefecundity:warm_coldcold:scalegen_selection -0.08 0.17
## trait_typesurvival:warm_coldcold:scalegen_selection -0.11 0.35
## trait_typebody_size:warm_coldwarm:scalegen_selection -0.04 0.07
## trait_typefecundity:warm_coldwarm:scalegen_selection -0.13 0.14
## trait_typesurvival:warm_coldwarm:scalegen_selection 0.03 0.36
## trait_typebody_size:warm_coldcold:scalegen_common_garden -0.05 0.62
## trait_typefecundity:warm_coldcold:scalegen_common_garden 0.78 1.06
## trait_typesurvival:warm_coldcold:scalegen_common_garden -0.18 1.24
## trait_typebody_size:warm_coldwarm:scalegen_common_garden -0.17 0.46
## trait_typefecundity:warm_coldwarm:scalegen_common_garden 0.01 0.04
## trait_typesurvival:warm_coldwarm:scalegen_common_garden 0.01 0.75
## l-95% CI u-95% CI Rhat
## trait_typebody_size:warm_coldcold -0.38 0.23 1.00
## trait_typefecundity:warm_coldcold -0.88 0.31 1.00
## trait_typesurvival:warm_coldcold -0.84 1.09 1.00
## trait_typebody_size:warm_coldwarm -0.29 0.23 1.00
## trait_typefecundity:warm_coldwarm -0.49 0.03 1.00
## trait_typesurvival:warm_coldwarm -0.19 0.67 1.00
## trait_typebody_size:warm_coldcold:assay_temp_diff -0.06 0.06 1.00
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.30 -0.09 1.00
## trait_typesurvival:warm_coldcold:assay_temp_diff -0.11 0.08 1.00
## trait_typebody_size:warm_coldwarm:assay_temp_diff -0.03 0.06 1.00
## trait_typefecundity:warm_coldwarm:assay_temp_diff -0.00 0.07 1.00
## trait_typesurvival:warm_coldwarm:assay_temp_diff -0.08 0.03 1.00
## trait_typebody_size:warm_coldcold:scaleselect_temp_diff -0.25 0.43 1.00
## trait_typefecundity:warm_coldcold:scaleselect_temp_diff -0.72 0.44 1.00
## trait_typesurvival:warm_coldcold:scaleselect_temp_diff -1.76 1.61 1.00
## trait_typebody_size:warm_coldwarm:scaleselect_temp_diff -0.38 0.03 1.00
## trait_typefecundity:warm_coldwarm:scaleselect_temp_diff -0.33 0.11 1.00
## trait_typesurvival:warm_coldwarm:scaleselect_temp_diff -0.29 0.28 1.00
## trait_typebody_size:warm_coldcold:scalegen_selection -0.17 0.31 1.00
## trait_typefecundity:warm_coldcold:scalegen_selection -0.42 0.26 1.00
## trait_typesurvival:warm_coldcold:scalegen_selection -0.80 0.59 1.00
## trait_typebody_size:warm_coldwarm:scalegen_selection -0.18 0.10 1.00
## trait_typefecundity:warm_coldwarm:scalegen_selection -0.41 0.15 1.00
## trait_typesurvival:warm_coldwarm:scalegen_selection -0.66 0.76 1.00
## trait_typebody_size:warm_coldcold:scalegen_common_garden -1.27 1.18 1.00
## trait_typefecundity:warm_coldcold:scalegen_common_garden -1.27 2.88 1.00
## trait_typesurvival:warm_coldcold:scalegen_common_garden -2.63 2.33 1.00
## trait_typebody_size:warm_coldwarm:scalegen_common_garden -1.06 0.73 1.00
## trait_typefecundity:warm_coldwarm:scalegen_common_garden -0.06 0.08 1.00
## trait_typesurvival:warm_coldwarm:scalegen_common_garden -1.46 1.48 1.00
## Bulk_ESS Tail_ESS
## trait_typebody_size:warm_coldcold 4092 4735
## trait_typefecundity:warm_coldcold 4850 5708
## trait_typesurvival:warm_coldcold 4449 4672
## trait_typebody_size:warm_coldwarm 3184 3857
## trait_typefecundity:warm_coldwarm 3037 4024
## trait_typesurvival:warm_coldwarm 4051 4019
## trait_typebody_size:warm_coldcold:assay_temp_diff 3053 4705
## trait_typefecundity:warm_coldcold:assay_temp_diff 5182 5436
## trait_typesurvival:warm_coldcold:assay_temp_diff 4312 5069
## trait_typebody_size:warm_coldwarm:assay_temp_diff 3133 4641
## trait_typefecundity:warm_coldwarm:assay_temp_diff 3229 4879
## trait_typesurvival:warm_coldwarm:assay_temp_diff 4695 5673
## trait_typebody_size:warm_coldcold:scaleselect_temp_diff 3511 4810
## trait_typefecundity:warm_coldcold:scaleselect_temp_diff 5220 5138
## trait_typesurvival:warm_coldcold:scaleselect_temp_diff 4013 4371
## trait_typebody_size:warm_coldwarm:scaleselect_temp_diff 3665 5246
## trait_typefecundity:warm_coldwarm:scaleselect_temp_diff 2875 4836
## trait_typesurvival:warm_coldwarm:scaleselect_temp_diff 4256 4342
## trait_typebody_size:warm_coldcold:scalegen_selection 3945 5379
## trait_typefecundity:warm_coldcold:scalegen_selection 5406 6015
## trait_typesurvival:warm_coldcold:scalegen_selection 4241 4808
## trait_typebody_size:warm_coldwarm:scalegen_selection 4182 5575
## trait_typefecundity:warm_coldwarm:scalegen_selection 2750 4380
## trait_typesurvival:warm_coldwarm:scalegen_selection 4376 4436
## trait_typebody_size:warm_coldcold:scalegen_common_garden 4993 5306
## trait_typefecundity:warm_coldcold:scalegen_common_garden 6715 5756
## trait_typesurvival:warm_coldcold:scalegen_common_garden 4269 5049
## trait_typebody_size:warm_coldwarm:scalegen_common_garden 4005 5288
## trait_typefecundity:warm_coldwarm:scalegen_common_garden 5139 5533
## trait_typesurvival:warm_coldwarm:scalegen_common_garden 5471 5371
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnVR_model_all_moderators, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(
# Simplify `trait_type` and `warm_cold` interactions
Parameter = gsub("trait_type([a-z_]+):warm_cold(cold|warm)", "\\1(\\2)", Parameter),
# Adjust for assay_temp_diff
Parameter = gsub("trait_type([a-z_]+)\\((cold|warm)\\):assay_temp_diff", "\\1(\\2): assay_temp_diff", Parameter),
# Remove "scale" from all moderator variables
Parameter = gsub("scale", "", Parameter)
) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~ round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size(cold) | -0.083 | -0.381 | 0.231 |
| fecundity(cold) | -0.281 | -0.882 | 0.314 |
| survival(cold) | 0.112 | -0.845 | 1.094 |
| body_size(warm) | -0.036 | -0.288 | 0.230 |
| fecundity(warm) | -0.228 | -0.491 | 0.028 |
| survival(warm) | 0.222 | -0.189 | 0.669 |
| body_size(cold):assay_temp_diff | 0.000 | -0.062 | 0.062 |
| fecundity(cold):assay_temp_diff | -0.198 | -0.300 | -0.093 |
| survival(cold):assay_temp_diff | -0.018 | -0.113 | 0.080 |
| body_size(warm):assay_temp_diff | 0.016 | -0.028 | 0.061 |
| fecundity(warm):assay_temp_diff | 0.033 | -0.002 | 0.069 |
| survival(warm):assay_temp_diff | -0.026 | -0.079 | 0.029 |
| body_size(cold):select_temp_diff | 0.092 | -0.251 | 0.435 |
| fecundity(cold):select_temp_diff | -0.149 | -0.716 | 0.438 |
| survival(cold):select_temp_diff | -0.033 | -1.761 | 1.613 |
| body_size(warm):select_temp_diff | -0.175 | -0.377 | 0.028 |
| fecundity(warm):select_temp_diff | -0.108 | -0.334 | 0.114 |
| survival(warm):select_temp_diff | 0.008 | -0.290 | 0.276 |
| body_size(cold):gen_selection | 0.070 | -0.172 | 0.307 |
| fecundity(cold):gen_selection | -0.075 | -0.415 | 0.264 |
| survival(cold):gen_selection | -0.112 | -0.800 | 0.589 |
| body_size(warm):gen_selection | -0.041 | -0.179 | 0.104 |
| fecundity(warm):gen_selection | -0.131 | -0.413 | 0.146 |
| survival(warm):gen_selection | 0.033 | -0.663 | 0.756 |
| body_size(cold):gen_common_garden | -0.048 | -1.269 | 1.184 |
| fecundity(cold):gen_common_garden | 0.781 | -1.271 | 2.883 |
| survival(cold):gen_common_garden | -0.181 | -2.627 | 2.330 |
| body_size(warm):gen_common_garden | -0.167 | -1.056 | 0.735 |
| fecundity(warm):gen_common_garden | 0.009 | -0.065 | 0.083 |
| survival(warm):gen_common_garden | 0.008 | -1.465 | 1.478 |
# Generate predicted values at different assay temperatures
emmeans_full_model <- as.data.frame(emmeans(
lnVR_model_all_moderators,
specs = ~ assay_temp_diff * select_temp_diff * gen_selection * gen_common_garden | trait_type * warm_cold,
at = list(assay_temp_diff = c(-8.5, -4, 0, 4, 5, -5, 17, 20)) # Predict at unique min, control, max values
))
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_assay = min(assay_temp_diff),
max_assay = max(assay_temp_diff),
control_assay = 0
)
emmeans_full_model$trait_type <- factor(emmeans_full_model$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_full_model$warm_cold <- factor(emmeans_full_model$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_full_model %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
assay_temp_diff == min_assay |
assay_temp_diff == max_assay |
assay_temp_diff == control_assay) %>%
dplyr::select(-min_assay, -max_assay, -control_assay) %>%
mutate(percentage_change = (exp(emmean) - 1) * 100,
percentage_lower_HPD = (exp(lower.HPD) - 1) * 100,
percentage_upper_HPD = (exp(upper.HPD) - 1) * 100) %>%
mutate(across(c(emmean,
lower.HPD,
upper.HPD,
percentage_change,
percentage_lower_HPD,
percentage_upper_HPD), ~ round(., 3))) %>% # Round numbers to 3 decimal points
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| assay_temp_diff | select_temp_diff | gen_selection | gen_common_garden | trait_type | warm_cold | emmean | lower.HPD | upper.HPD | percentage_change | percentage_lower_HPD | percentage_upper_HPD |
|---|---|---|---|---|---|---|---|---|---|---|---|
| -8.5 | 5.64645 | 29.0084 | 2.710084 | Body size | Cold | -0.078 | -0.650 | 0.552 | -7.491 | -47.773 | 73.668 |
| 0.0 | 5.64645 | 29.0084 | 2.710084 | Body size | Cold | -0.085 | -0.392 | 0.216 | -8.108 | -32.460 | 24.050 |
| 5.0 | 5.64645 | 29.0084 | 2.710084 | Body size | Cold | -0.087 | -0.536 | 0.345 | -8.315 | -41.511 | 41.148 |
| -8.5 | 5.64645 | 29.0084 | 2.710084 | Fecundity | Cold | 1.413 | 0.302 | 2.480 | 310.635 | 35.251 | 1094.609 |
| 0.0 | 5.64645 | 29.0084 | 2.710084 | Fecundity | Cold | -0.280 | -0.865 | 0.324 | -24.434 | -57.915 | 38.311 |
| 5.0 | 5.64645 | 29.0084 | 2.710084 | Fecundity | Cold | -1.275 | -2.009 | -0.482 | -72.052 | -86.592 | -38.222 |
| -8.5 | 5.64645 | 29.0084 | 2.710084 | Survival | Cold | 0.258 | -1.001 | 1.374 | 29.383 | -63.251 | 294.925 |
| 0.0 | 5.64645 | 29.0084 | 2.710084 | Survival | Cold | 0.108 | -0.818 | 1.107 | 11.434 | -55.875 | 202.606 |
| 4.0 | 5.64645 | 29.0084 | 2.710084 | Survival | Cold | 0.034 | -1.058 | 1.090 | 3.435 | -65.279 | 197.415 |
| 0.0 | 5.64645 | 29.0084 | 2.710084 | Body size | Warm | -0.038 | -0.288 | 0.230 | -3.732 | -25.040 | 25.816 |
| -5.0 | 5.64645 | 29.0084 | 2.710084 | Body size | Warm | -0.116 | -0.524 | 0.296 | -10.944 | -40.778 | 34.449 |
| 17.0 | 5.64645 | 29.0084 | 2.710084 | Body size | Warm | 0.232 | -0.426 | 0.930 | 26.127 | -34.677 | 153.410 |
| -4.0 | 5.64645 | 29.0084 | 2.710084 | Fecundity | Warm | -0.359 | -0.706 | 0.002 | -30.160 | -50.615 | 0.224 |
| 0.0 | 5.64645 | 29.0084 | 2.710084 | Fecundity | Warm | -0.227 | -0.494 | 0.024 | -20.270 | -38.967 | 2.465 |
| 20.0 | 5.64645 | 29.0084 | 2.710084 | Fecundity | Warm | 0.429 | -0.198 | 0.999 | 53.496 | -17.938 | 171.680 |
| 0.0 | 5.64645 | 29.0084 | 2.710084 | Survival | Warm | 0.216 | -0.216 | 0.639 | 24.172 | -19.460 | 89.382 |
| -5.0 | 5.64645 | 29.0084 | 2.710084 | Survival | Warm | 0.348 | -0.204 | 0.907 | 41.687 | -18.484 | 147.766 |
| 17.0 | 5.64645 | 29.0084 | 2.710084 | Survival | Warm | -0.230 | -1.087 | 0.704 | -20.537 | -66.292 | 102.194 |
Data visualisation
Marginal means
# Generate predictions at assay_temp_diff = 0, or 5 degrees below or above the control temperatures
emmeans_full_model <- as.data.frame(emmeans(
lnVR_model_all_moderators,
specs = ~ assay_temp_diff * select_temp_diff * gen_selection * gen_common_garden | trait_type * warm_cold,
at = list(assay_temp_diff = c(-5, 0, 5) # Re-acclimated to control, or 5 degrees above/below the contol
))) # 2 generations of common garden
# Rename variable levels
emmeans_full_model$trait_type <- factor(emmeans_full_model$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_full_model$warm_cold <- factor(emmeans_full_model$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
emmeans_full_model
## assay_temp_diff select_temp_diff gen_selection gen_common_garden trait_type
## 1 -5 5.64645 29.0084 2.710084 Body size
## 2 0 5.64645 29.0084 2.710084 Body size
## 3 5 5.64645 29.0084 2.710084 Body size
## 4 -5 5.64645 29.0084 2.710084 Fecundity
## 5 0 5.64645 29.0084 2.710084 Fecundity
## 6 5 5.64645 29.0084 2.710084 Fecundity
## 7 -5 5.64645 29.0084 2.710084 Survival
## 8 0 5.64645 29.0084 2.710084 Survival
## 9 5 5.64645 29.0084 2.710084 Survival
## 10 -5 5.64645 29.0084 2.710084 Body size
## 11 0 5.64645 29.0084 2.710084 Body size
## 12 5 5.64645 29.0084 2.710084 Body size
## 13 -5 5.64645 29.0084 2.710084 Fecundity
## 14 0 5.64645 29.0084 2.710084 Fecundity
## 15 5 5.64645 29.0084 2.710084 Fecundity
## 16 -5 5.64645 29.0084 2.710084 Survival
## 17 0 5.64645 29.0084 2.710084 Survival
## 18 5 5.64645 29.0084 2.710084 Survival
## warm_cold emmean lower.HPD upper.HPD
## 1 Cold -0.08064238 -0.4995174 0.35636726
## 2 Cold -0.08455378 -0.3924526 0.21551387
## 3 Cold -0.08680692 -0.5363397 0.34463802
## 4 Cold 0.71680891 -0.1019656 1.53187570
## 5 Cold -0.28016725 -0.8654796 0.32433172
## 6 Cold -1.27480792 -2.0093152 -0.48161845
## 7 Cold 0.19700463 -0.8468015 1.19837676
## 8 Cold 0.10826501 -0.8181482 1.10726151
## 9 Cold 0.01652157 -1.0856681 1.15063228
## 10 Warm -0.11590848 -0.5238690 0.29601598
## 11 Warm -0.03803553 -0.2882221 0.22964953
## 12 Warm 0.04308212 -0.1989313 0.30604115
## 13 Warm -0.39198484 -0.7850204 -0.02030154
## 14 Warm -0.22652041 -0.4937523 0.02435364
## 15 Warm -0.06490229 -0.2884375 0.16238805
## 16 Warm 0.34845267 -0.2043654 0.90731380
## 17 Warm 0.21649458 -0.2164120 0.63859658
## 18 Warm 0.08608553 -0.3347825 0.53397689
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75,
lwd=1) +
geom_quasirandom(data = data, # Plot effect sizes, scaled by precision
aes(x = trait_type:warm_cold,
y = lnVR,
fill = trait_type:warm_cold,
size = 1/sqrt(var_lnVR)),
color = "black",
shape = 21,
width = 0.3,
alpha = 0.3) +
geom_errorbar(data = filter(emmeans_full_model,
assay_temp_diff == "-5"), # 5 degrees below control (background)
aes(x = trait_type:warm_cold,
ymin = lower.HPD,
ymax = upper.HPD),
color="white",
size = 2.75,
width = 0.16,
position = position_nudge(x=-0.25)) +
geom_point(data = filter(emmeans_full_model,
assay_temp_diff == "-5"), # 5 degrees below control (background)
aes(x = trait_type:warm_cold,
y = emmean),
shape = 21,
size = 4.5,
stroke = 2,
color="white",
fill = "white",
position = position_nudge(x=-0.25)) +
geom_errorbar(data = filter(emmeans_full_model,
assay_temp_diff == "-5"), # 5 degrees below control (credible intervals)
aes(x = trait_type:warm_cold,
ymin = lower.HPD,
ymax = upper.HPD),
color="#06B4BA",
size = 2,
width = 0.12,
position = position_nudge(x=-0.25)) +
geom_point(data = filter(emmeans_full_model,
assay_temp_diff == "-5"), # 5 degrees below control (point estimates)
aes(x = trait_type:warm_cold,
y = emmean),
shape = 21,
size = 3.5,
stroke = 2,
color="#06B4BA",
fill = "white",
position = position_nudge(x=-0.25)) +
geom_errorbar(data = filter(emmeans_full_model,
assay_temp_diff == "0"), # control temperature (background)
aes(x = trait_type:warm_cold,
ymin = lower.HPD,
ymax = upper.HPD),
color="white",
size = 2.75,
width = 0.16,
position = position_nudge(x=0)) +
geom_point(data = filter(emmeans_full_model,
assay_temp_diff == "0"), # control temperature (background)
aes(x = trait_type:warm_cold,
y = emmean),
shape = 22,
size = 4.5,
stroke = 2,
color="white",
fill = "white",
position = position_nudge(x=0)) +
geom_errorbar(data = filter(emmeans_full_model,
assay_temp_diff == "0"), # control temperature (credible intervals)
aes(x = trait_type:warm_cold,
ymin = lower.HPD,
ymax = upper.HPD),
color="black",
size = 2,
width = 0.12,
position = position_nudge(x=0)) +
geom_point(data = filter(emmeans_full_model,
assay_temp_diff == "0"), # control temperature (point estimates)
aes(x = trait_type:warm_cold,
y = emmean),
shape = 22,
size = 3.5,
stroke = 2,
color="black",
fill = "white",
position = position_nudge(x=0)) +
geom_errorbar(data = filter(emmeans_full_model,
assay_temp_diff == "5"), # 5 degrees above control (background)
aes(x = trait_type:warm_cold,
ymin = lower.HPD,
ymax = upper.HPD),
color="white",
size = 2.75,
width = 0.16,
position = position_nudge(x=0.25)) +
geom_point(data = filter(emmeans_full_model,
assay_temp_diff == "5"), # 5 degrees above control (background)
aes(x = trait_type:warm_cold,
y = emmean),
shape = 23,
size = 4.5,
stroke = 2,
color="white",
fill = "white",
position = position_nudge(x=0.25)) +
geom_errorbar(data = filter(emmeans_full_model,
assay_temp_diff == "5"), # 5 degrees above control (credible intervals)
aes(x = trait_type:warm_cold,
ymin = lower.HPD,
ymax = upper.HPD),
color="#E80756",
size = 2,
width = 0.12,
position = position_nudge(x=0.25)) +
geom_point(data = filter(emmeans_full_model,
assay_temp_diff == "5"), # 5 degrees above control (point estimates)
aes(x = trait_type:warm_cold,
y = emmean),
shape = 23,
size = 3.5,
stroke = 2,
color="#E80756",
fill = "white",
position = position_nudge(x=0.25)) +
geom_text(data = sample_sizes, # Sample size annotations
aes(x = trait_type:warm_cold,
y = 3,
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1, size = 5, show.legend = FALSE) +
theme_bw() + # Customize the plot
labs(y = "lnVR", x = "") +
scale_size_continuous(range = c(2, 8))+
scale_fill_manual(values = c("#06B4BA", "#E80756", "#06B4BA", "#E80756", "#06B4BA", "#E80756"))+
scale_color_manual(values = c("Cold" = "#06B4BA", "Warm" = "#E80756")) + # Text colour
theme(text = element_text(size = 20, color = "black"),
legend.title = ggplot2::element_text(size = 16),
legend.text = ggplot2::element_text(size = 14),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5)) +
guides(fill = "none", size = "none")+
coord_flip() +
ylim(-3.5, 3.5)
ggsave(file = "fig/lnVR_all_moderators_marginal_means.png", width = 12, height = 8, dpi = 500)
Overall trend with assay temperature
# Generate predictions for all assay temperatures
emmeans_full_model_assay <- as.data.frame(emmeans(
lnVR_model_all_moderators,
specs = ~ assay_temp_diff * select_temp_diff * gen_selection * gen_common_garden | trait_type * warm_cold,
at = list(assay_temp_diff = seq(min(data$assay_temp_diff), max(data$assay_temp_diff), by = 0.5) # Re-acclimated to control, or 5 degrees above/below the contol
)))
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_assay_temp_diff = min(assay_temp_diff),
max_assay_temp_diff = max(assay_temp_diff)
)
# Rename variable levels
emmeans_full_model_assay$trait_type <- factor(emmeans_full_model_assay$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_full_model_assay$warm_cold <- factor(emmeans_full_model_assay$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_full_model_assay <- emmeans_full_model_assay %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
assay_temp_diff >= min_assay_temp_diff,
assay_temp_diff <= max_assay_temp_diff
)
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_vline(xintercept = 0, # Vertical line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = assay_temp_diff,
y = lnVR,
size = 1/sqrt(var_lnVR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_full_model_assay, # Shaded area for credible intervals
aes(x = assay_temp_diff,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_full_model_assay, # Predicted regression line
aes(y = emmean,
x = assay_temp_diff,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -2, -2.75), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Assay temperature difference", y = "lnVR",
col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 25, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 22)) +
guides(fill = "none", size = "none")+
coord_cartesian(ylim = c(-3.5, 3.5))
ggsave(file = "fig/lnVR_all_moderators_assay_temp_diff.png", width = 12, height = 10, dpi = 500)
Publication bias
Changes in mean responses (lnRR)
Egger’s regression
Model specification
# Model specification
formula <- bf(lnRR ~ 0 + trait_type:warm_cold +
trait_type:warm_cold:assay_temp_diff +
trait_type:warm_cold:var_lnRR + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnRR)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnRR_model_pub_bias_var <- brm(formula,
family = gaussian(),
data = data,
data2 = list(phylo_matrix = phylo_matrix,
VCV_lnRR = VCV_lnRR),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnRR_model_pub_bias_var, file = "RData/lnRR_model_pub_bias_var.rds")
Model output
# Load model
lnRR_model_pub_bias_var <- readRDS("RData/lnRR_model_pub_bias_var.rds")
# Display model output
summary(lnRR_model_pub_bias_var)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnRR ~ 0 + trait_type:warm_cold + trait_type:warm_cold:assay_temp_diff + trait_type:warm_cold:var_lnRR + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnRR)
## Data: data (Number of observations: 476)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 88)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.02 0.01 0.00 0.04 1.01 625 928
##
## ~obs (Number of levels: 476)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.05 0.00 0.04 0.06 1.00 1762 3592
##
## ~phylogeny (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.03 0.02 0.00 0.07 1.00 1805 3058
##
## ~ref (Number of levels: 44)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.01 0.01 0.00
## sd(trait_typefecundity) 0.21 0.04 0.15
## sd(trait_typesurvival) 0.13 0.03 0.07
## cor(trait_typebody_size,trait_typefecundity) -0.18 0.46 -0.92
## cor(trait_typebody_size,trait_typesurvival) 0.02 0.49 -0.86
## cor(trait_typefecundity,trait_typesurvival) -0.23 0.50 -0.94
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.03 1.00 1307 2565
## sd(trait_typefecundity) 0.29 1.00 3203 5556
## sd(trait_typesurvival) 0.20 1.00 3705 5525
## cor(trait_typebody_size,trait_typefecundity) 0.77 1.02 244 684
## cor(trait_typebody_size,trait_typesurvival) 0.88 1.01 789 2165
## cor(trait_typefecundity,trait_typesurvival) 0.77 1.00 970 2410
##
## ~species (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.02 0.01 0.00 0.05 1.00 1987 3132
##
## Regression Coefficients:
## Estimate Est.Error l-95% CI
## trait_typebody_size:warm_coldcold 0.03 0.02 -0.01
## trait_typefecundity:warm_coldcold -0.30 0.08 -0.46
## trait_typesurvival:warm_coldcold -0.09 0.07 -0.22
## trait_typebody_size:warm_coldwarm 0.00 0.02 -0.04
## trait_typefecundity:warm_coldwarm -0.04 0.05 -0.13
## trait_typesurvival:warm_coldwarm -0.02 0.04 -0.10
## trait_typebody_size:warm_coldcold:assay_temp_diff 0.00 0.00 -0.00
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.07 0.01 -0.08
## trait_typesurvival:warm_coldcold:assay_temp_diff -0.04 0.00 -0.05
## trait_typebody_size:warm_coldwarm:assay_temp_diff -0.00 0.00 -0.00
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.00 0.00 -0.00
## trait_typesurvival:warm_coldwarm:assay_temp_diff 0.00 0.00 -0.00
## trait_typebody_size:warm_coldcold:var_lnRR -37.45 16.09 -69.80
## trait_typefecundity:warm_coldcold:var_lnRR 9.26 3.29 2.90
## trait_typesurvival:warm_coldcold:var_lnRR 2.89 4.72 -6.48
## trait_typebody_size:warm_coldwarm:var_lnRR -6.01 3.50 -12.92
## trait_typefecundity:warm_coldwarm:var_lnRR 0.20 0.18 -0.16
## trait_typesurvival:warm_coldwarm:var_lnRR 0.57 0.46 -0.33
## u-95% CI Rhat Bulk_ESS
## trait_typebody_size:warm_coldcold 0.08 1.00 2091
## trait_typefecundity:warm_coldcold -0.14 1.00 3390
## trait_typesurvival:warm_coldcold 0.06 1.00 3209
## trait_typebody_size:warm_coldwarm 0.05 1.00 2441
## trait_typefecundity:warm_coldwarm 0.05 1.00 2745
## trait_typesurvival:warm_coldwarm 0.06 1.00 3477
## trait_typebody_size:warm_coldcold:assay_temp_diff 0.01 1.00 1674
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.06 1.00 4928
## trait_typesurvival:warm_coldcold:assay_temp_diff -0.03 1.00 3469
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.00 1.00 3007
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.01 1.00 3512
## trait_typesurvival:warm_coldwarm:assay_temp_diff 0.01 1.00 5080
## trait_typebody_size:warm_coldcold:var_lnRR -6.12 1.00 3459
## trait_typefecundity:warm_coldcold:var_lnRR 15.76 1.00 6363
## trait_typesurvival:warm_coldcold:var_lnRR 12.23 1.00 5556
## trait_typebody_size:warm_coldwarm:var_lnRR 0.80 1.00 7407
## trait_typefecundity:warm_coldwarm:var_lnRR 0.56 1.00 11077
## trait_typesurvival:warm_coldwarm:var_lnRR 1.47 1.00 10992
## Tail_ESS
## trait_typebody_size:warm_coldcold 2920
## trait_typefecundity:warm_coldcold 4988
## trait_typesurvival:warm_coldcold 4578
## trait_typebody_size:warm_coldwarm 2755
## trait_typefecundity:warm_coldwarm 3995
## trait_typesurvival:warm_coldwarm 4608
## trait_typebody_size:warm_coldcold:assay_temp_diff 3045
## trait_typefecundity:warm_coldcold:assay_temp_diff 6254
## trait_typesurvival:warm_coldcold:assay_temp_diff 4987
## trait_typebody_size:warm_coldwarm:assay_temp_diff 4786
## trait_typefecundity:warm_coldwarm:assay_temp_diff 5052
## trait_typesurvival:warm_coldwarm:assay_temp_diff 5221
## trait_typebody_size:warm_coldcold:var_lnRR 5560
## trait_typefecundity:warm_coldcold:var_lnRR 6420
## trait_typesurvival:warm_coldcold:var_lnRR 5414
## trait_typebody_size:warm_coldwarm:var_lnRR 6098
## trait_typefecundity:warm_coldwarm:var_lnRR 6408
## trait_typesurvival:warm_coldwarm:var_lnRR 6471
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnRR_model_pub_bias_var, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(Parameter = gsub("trait_type([a-z_]+):warm_cold(cold|warm)", "\\1(\\2)",
Parameter), Parameter = gsub("trait_type([a-z_]+)\\(warm\\):assay_temp_diff",
"\\1(warm): assay_temp_diff", Parameter)) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size(cold) | 0.034 | -0.013 | 0.082 |
| fecundity(cold) | -0.298 | -0.456 | -0.143 |
| survival(cold) | -0.087 | -0.222 | 0.058 |
| body_size(warm) | 0.003 | -0.043 | 0.048 |
| fecundity(warm) | -0.040 | -0.133 | 0.055 |
| survival(warm) | -0.019 | -0.096 | 0.058 |
| body_size(cold):assay_temp_diff | 0.001 | -0.003 | 0.006 |
| fecundity(cold):assay_temp_diff | -0.066 | -0.076 | -0.055 |
| survival(cold):assay_temp_diff | -0.045 | -0.054 | -0.035 |
| body_size(warm):assay_temp_diff | -0.001 | -0.004 | 0.003 |
| fecundity(warm):assay_temp_diff | 0.004 | -0.003 | 0.012 |
| survival(warm):assay_temp_diff | 0.005 | -0.002 | 0.011 |
| body_size(cold):var_lnRR | -37.450 | -69.801 | -6.116 |
| fecundity(cold):var_lnRR | 9.258 | 2.897 | 15.765 |
| survival(cold):var_lnRR | 2.893 | -6.477 | 12.231 |
| body_size(warm):var_lnRR | -6.010 | -12.925 | 0.805 |
| fecundity(warm):var_lnRR | 0.204 | -0.161 | 0.562 |
| survival(warm):var_lnRR | 0.575 | -0.330 | 1.468 |
Data visualisation
# Generate predictions
emmeans_pub_bias_var <- as.data.frame(emmeans(
lnRR_model_pub_bias_var,
specs = ~ var_lnRR | trait_type * warm_cold,
at = list(var_lnRR = seq(0, max(data$var_lnRR), by = 0.1),
assay_temp_diff = 0))
) # Conditional effects on assay_temp_diff = 0
emmeans_pub_bias_var$trait_type <- factor(emmeans_pub_bias_var$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_pub_bias_var$warm_cold <- factor(emmeans_pub_bias_var$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = var_lnRR,
y = lnRR,
size = 1/sqrt(var_lnRR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.001)) +
geom_ribbon(data = emmeans_pub_bias_var, # Shaded area for credible intervals
aes(x = var_lnRR,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_pub_bias_var, # Predicted regression line
aes(y = emmean,
x = var_lnRR,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -0.8, -1.3), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Sampling variance of lnRR", y = "lnRR", col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 25, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 22)) +
guides(fill = "none", size = "none")+
coord_cartesian(ylim = c(-1.5, 1.5), xlim = c(0, 0.3))
ggsave(file = "fig/lnRR_pub_bias_var.png", width = 12, height = 7, dpi = 500)
Publication year
Model specification
# Model specification
formula <- bf(lnRR ~ 0 + trait_type:warm_cold +
trait_type:warm_cold:assay_temp_diff +
trait_type:warm_cold:scale(pub_year) + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnRR)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnRR_model_pub_bias_year <- brm(formula,
family = gaussian(),
data = data,
data2 = list(phylo_matrix = phylo_matrix,
VCV_lnRR = VCV_lnRR),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnRR_model_pub_bias_year, file = "RData/lnRR_model_pub_bias_year.rds")
Model output
# Load model
lnRR_model_pub_bias_year <- readRDS("RData/lnRR_model_pub_bias_year.rds")
# Display model output
summary(lnRR_model_pub_bias_year)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnRR ~ 0 + trait_type:warm_cold + trait_type:warm_cold:assay_temp_diff + trait_type:warm_cold:scale(pub_year) + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnRR)
## Data: data (Number of observations: 476)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 88)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.02 0.01 0.00 0.04 1.01 636 935
##
## ~obs (Number of levels: 476)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.05 0.00 0.04 0.06 1.00 1762 2828
##
## ~phylogeny (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.02 0.02 0.00 0.06 1.00 2547 3963
##
## ~ref (Number of levels: 44)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.01 0.01 0.00
## sd(trait_typefecundity) 0.20 0.04 0.14
## sd(trait_typesurvival) 0.12 0.03 0.07
## cor(trait_typebody_size,trait_typefecundity) -0.09 0.48 -0.88
## cor(trait_typebody_size,trait_typesurvival) 0.09 0.49 -0.83
## cor(trait_typefecundity,trait_typesurvival) -0.17 0.48 -0.93
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.03 1.00 1842 3407
## sd(trait_typefecundity) 0.28 1.00 3405 5116
## sd(trait_typesurvival) 0.19 1.00 3689 5281
## cor(trait_typebody_size,trait_typefecundity) 0.82 1.01 457 1387
## cor(trait_typebody_size,trait_typesurvival) 0.89 1.01 928 2233
## cor(trait_typefecundity,trait_typesurvival) 0.73 1.00 1472 3245
##
## ~species (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.02 0.01 0.00 0.04 1.00 2734 4675
##
## Regression Coefficients:
## Estimate Est.Error l-95% CI
## trait_typebody_size:warm_coldcold 0.01 0.02 -0.03
## trait_typefecundity:warm_coldcold -0.10 0.07 -0.23
## trait_typesurvival:warm_coldcold -0.01 0.08 -0.16
## trait_typebody_size:warm_coldwarm -0.00 0.02 -0.05
## trait_typefecundity:warm_coldwarm -0.03 0.05 -0.12
## trait_typesurvival:warm_coldwarm -0.02 0.04 -0.09
## trait_typebody_size:warm_coldcold:assay_temp_diff 0.00 0.00 -0.00
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.06 0.01 -0.07
## trait_typesurvival:warm_coldcold:assay_temp_diff -0.04 0.00 -0.05
## trait_typebody_size:warm_coldwarm:assay_temp_diff -0.00 0.00 -0.00
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.00 0.00 -0.00
## trait_typesurvival:warm_coldwarm:assay_temp_diff 0.00 0.00 -0.00
## trait_typebody_size:warm_coldcold:scalepub_year -0.01 0.01 -0.03
## trait_typefecundity:warm_coldcold:scalepub_year 0.16 0.05 0.06
## trait_typesurvival:warm_coldcold:scalepub_year 0.06 0.06 -0.05
## trait_typebody_size:warm_coldwarm:scalepub_year 0.01 0.01 -0.01
## trait_typefecundity:warm_coldwarm:scalepub_year 0.04 0.04 -0.04
## trait_typesurvival:warm_coldwarm:scalepub_year 0.04 0.04 -0.03
## u-95% CI Rhat Bulk_ESS
## trait_typebody_size:warm_coldcold 0.06 1.00 2334
## trait_typefecundity:warm_coldcold 0.03 1.00 5017
## trait_typesurvival:warm_coldcold 0.15 1.00 5895
## trait_typebody_size:warm_coldwarm 0.04 1.00 2820
## trait_typefecundity:warm_coldwarm 0.06 1.00 4453
## trait_typesurvival:warm_coldwarm 0.07 1.00 4478
## trait_typebody_size:warm_coldcold:assay_temp_diff 0.01 1.00 2291
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.05 1.00 4921
## trait_typesurvival:warm_coldcold:assay_temp_diff -0.04 1.00 3913
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.00 1.00 3929
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.01 1.00 3076
## trait_typesurvival:warm_coldwarm:assay_temp_diff 0.01 1.00 6488
## trait_typebody_size:warm_coldcold:scalepub_year 0.01 1.00 2230
## trait_typefecundity:warm_coldcold:scalepub_year 0.26 1.00 4980
## trait_typesurvival:warm_coldcold:scalepub_year 0.17 1.00 4804
## trait_typebody_size:warm_coldwarm:scalepub_year 0.03 1.00 3189
## trait_typefecundity:warm_coldwarm:scalepub_year 0.12 1.00 4032
## trait_typesurvival:warm_coldwarm:scalepub_year 0.12 1.00 4954
## Tail_ESS
## trait_typebody_size:warm_coldcold 2788
## trait_typefecundity:warm_coldcold 5695
## trait_typesurvival:warm_coldcold 6214
## trait_typebody_size:warm_coldwarm 2872
## trait_typefecundity:warm_coldwarm 4859
## trait_typesurvival:warm_coldwarm 5133
## trait_typebody_size:warm_coldcold:assay_temp_diff 4090
## trait_typefecundity:warm_coldcold:assay_temp_diff 6315
## trait_typesurvival:warm_coldcold:assay_temp_diff 6167
## trait_typebody_size:warm_coldwarm:assay_temp_diff 4832
## trait_typefecundity:warm_coldwarm:assay_temp_diff 5716
## trait_typesurvival:warm_coldwarm:assay_temp_diff 5181
## trait_typebody_size:warm_coldcold:scalepub_year 3673
## trait_typefecundity:warm_coldcold:scalepub_year 5820
## trait_typesurvival:warm_coldcold:scalepub_year 5389
## trait_typebody_size:warm_coldwarm:scalepub_year 4412
## trait_typefecundity:warm_coldwarm:scalepub_year 4828
## trait_typesurvival:warm_coldwarm:scalepub_year 5519
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnRR_model_pub_bias_year, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(
# Simplify `trait_type` and `warm_cold` interactions
Parameter = gsub("trait_type([a-z_]+):warm_cold(cold|warm)", "\\1(\\2)", Parameter),
# Adjust for assay_temp_diff
Parameter = gsub("trait_type([a-z_]+)\\((cold|warm)\\):assay_temp_diff", "\\1(\\2): assay_temp_diff", Parameter),
# Remove "scale" from pub_year
Parameter = gsub("scalepub_year", "pub_year", Parameter)
) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~ round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size(cold) | 0.011 | -0.035 | 0.056 |
| fecundity(cold) | -0.096 | -0.231 | 0.032 |
| survival(cold) | -0.007 | -0.164 | 0.152 |
| body_size(warm) | -0.005 | -0.049 | 0.036 |
| fecundity(warm) | -0.032 | -0.124 | 0.059 |
| survival(warm) | -0.016 | -0.093 | 0.066 |
| body_size(cold):assay_temp_diff | 0.002 | -0.003 | 0.007 |
| fecundity(cold):assay_temp_diff | -0.063 | -0.074 | -0.053 |
| survival(cold):assay_temp_diff | -0.045 | -0.054 | -0.035 |
| body_size(warm):assay_temp_diff | -0.001 | -0.004 | 0.002 |
| fecundity(warm):assay_temp_diff | 0.004 | -0.003 | 0.011 |
| survival(warm):assay_temp_diff | 0.005 | -0.002 | 0.011 |
| body_size(cold):pub_year | -0.008 | -0.030 | 0.013 |
| fecundity(cold):pub_year | 0.157 | 0.062 | 0.256 |
| survival(cold):pub_year | 0.063 | -0.054 | 0.175 |
| body_size(warm):pub_year | 0.006 | -0.015 | 0.026 |
| fecundity(warm):pub_year | 0.038 | -0.039 | 0.119 |
| survival(warm):pub_year | 0.042 | -0.033 | 0.117 |
Data visualisation
# Generate predictions
emmeans_pub_bias_year <- as.data.frame(emmeans(
lnRR_model_pub_bias_year,
specs = ~ pub_year | trait_type * warm_cold,
at = list(pub_year = seq(min(data$pub_year), max(data$pub_year), by = 1),
assay_temp_diff = 0))
) # Conditional effects on assay_temp_diff = 0
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_pub_year = min(pub_year),
max_pub_year = max(pub_year)
)
emmeans_pub_bias_year$trait_type <- factor(emmeans_pub_bias_year$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_pub_bias_year$warm_cold <- factor(emmeans_pub_bias_year$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_pub_bias_year <- emmeans_pub_bias_year %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
pub_year >= min_pub_year,
pub_year <= max_pub_year
)
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = pub_year,
y = lnRR,
size = 1/sqrt(pub_year),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_pub_bias_year, # Shaded area for credible intervals
aes(x = pub_year,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_pub_bias_year, # Predicted regression line
aes(y = emmean,
x = pub_year,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -0.9, -1.3), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Sampling variance of lnRR", y = "lnRR", col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 25, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 22)) +
guides(fill = "none", size = "none") +
coord_cartesian(ylim = c(-1.5, 1.5))
ggsave(file = "fig/lnRR_pub_bias_year.png", width = 12, height = 7, dpi = 500)
Changes in relative trait variance (lnCVR)
Egger’s regression
Model specification
# Model specification
formula <- bf(lnCVR ~ 0 + trait_type:warm_cold +
trait_type:warm_cold:assay_temp_diff +
trait_type:warm_cold:var_lnCVR + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnCVR)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnCVR_model_pub_bias_var <- brm(formula,
family = gaussian(),
data = data,
data2 = list(phylo_matrix = phylo_matrix,
VCV_lnCVR = VCV_lnCVR),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnCVR_model_pub_bias_var, file = "RData/lnCVR_model_pub_bias_var.rds")
Model output
# Load model
lnCVR_model_pub_bias_var <- readRDS("RData/lnCVR_model_pub_bias_var.rds")
# Display model output
summary(lnCVR_model_pub_bias_var)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnCVR ~ 0 + trait_type:warm_cold + trait_type:warm_cold:assay_temp_diff + trait_type:warm_cold:var_lnCVR + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnCVR)
## Data: data (Number of observations: 476)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 88)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.10 0.05 0.01 0.20 1.00 1063 2440
##
## ~obs (Number of levels: 476)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.39 0.04 0.32 0.47 1.00 1981 3628
##
## ~phylogeny (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.09 0.07 0.00 0.27 1.00 2920 3914
##
## ~ref (Number of levels: 44)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.06 0.05 0.00
## sd(trait_typefecundity) 0.11 0.09 0.00
## sd(trait_typesurvival) 0.56 0.16 0.28
## cor(trait_typebody_size,trait_typefecundity) 0.06 0.49 -0.85
## cor(trait_typebody_size,trait_typesurvival) -0.01 0.50 -0.86
## cor(trait_typefecundity,trait_typesurvival) -0.01 0.44 -0.83
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.18 1.00 3284 3493
## sd(trait_typefecundity) 0.31 1.00 1690 3775
## sd(trait_typesurvival) 0.92 1.00 2562 3420
## cor(trait_typebody_size,trait_typefecundity) 0.89 1.00 3344 4650
## cor(trait_typebody_size,trait_typesurvival) 0.88 1.00 962 2589
## cor(trait_typefecundity,trait_typesurvival) 0.82 1.00 1421 3139
##
## ~species (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.06 0.05 0.00 0.19 1.00 2752 4177
##
## Regression Coefficients:
## Estimate Est.Error l-95% CI
## trait_typebody_size:warm_coldcold -0.07 0.15 -0.36
## trait_typefecundity:warm_coldcold -0.06 0.29 -0.63
## trait_typesurvival:warm_coldcold 0.28 0.83 -1.37
## trait_typebody_size:warm_coldwarm 0.06 0.11 -0.15
## trait_typefecundity:warm_coldwarm -0.05 0.11 -0.25
## trait_typesurvival:warm_coldwarm 0.25 0.20 -0.13
## trait_typebody_size:warm_coldcold:assay_temp_diff -0.01 0.02 -0.05
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.15 0.04 -0.23
## trait_typesurvival:warm_coldcold:assay_temp_diff 0.03 0.04 -0.05
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.00 0.02 -0.03
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.02 0.02 -0.01
## trait_typesurvival:warm_coldwarm:assay_temp_diff -0.02 0.02 -0.07
## trait_typebody_size:warm_coldcold:var_lnCVR 0.29 0.47 -0.63
## trait_typefecundity:warm_coldcold:var_lnCVR -0.61 0.59 -1.74
## trait_typesurvival:warm_coldcold:var_lnCVR -0.39 1.80 -3.98
## trait_typebody_size:warm_coldwarm:var_lnCVR -0.01 0.12 -0.25
## trait_typefecundity:warm_coldwarm:var_lnCVR 0.01 0.03 -0.05
## trait_typesurvival:warm_coldwarm:var_lnCVR -0.03 0.06 -0.14
## u-95% CI Rhat Bulk_ESS
## trait_typebody_size:warm_coldcold 0.24 1.00 2396
## trait_typefecundity:warm_coldcold 0.51 1.00 3133
## trait_typesurvival:warm_coldcold 1.94 1.00 2648
## trait_typebody_size:warm_coldwarm 0.29 1.00 2541
## trait_typefecundity:warm_coldwarm 0.17 1.00 2817
## trait_typesurvival:warm_coldwarm 0.65 1.00 3752
## trait_typebody_size:warm_coldcold:assay_temp_diff 0.03 1.00 3328
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.07 1.00 5635
## trait_typesurvival:warm_coldcold:assay_temp_diff 0.10 1.00 5175
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.03 1.00 4733
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.05 1.00 4426
## trait_typesurvival:warm_coldwarm:assay_temp_diff 0.02 1.00 6333
## trait_typebody_size:warm_coldcold:var_lnCVR 1.22 1.00 3718
## trait_typefecundity:warm_coldcold:var_lnCVR 0.54 1.00 4085
## trait_typesurvival:warm_coldcold:var_lnCVR 3.15 1.00 2748
## trait_typebody_size:warm_coldwarm:var_lnCVR 0.23 1.00 8676
## trait_typefecundity:warm_coldwarm:var_lnCVR 0.06 1.00 11131
## trait_typesurvival:warm_coldwarm:var_lnCVR 0.08 1.00 11184
## Tail_ESS
## trait_typebody_size:warm_coldcold 3574
## trait_typefecundity:warm_coldcold 4664
## trait_typesurvival:warm_coldcold 3030
## trait_typebody_size:warm_coldwarm 2873
## trait_typefecundity:warm_coldwarm 2827
## trait_typesurvival:warm_coldwarm 4491
## trait_typebody_size:warm_coldcold:assay_temp_diff 4370
## trait_typefecundity:warm_coldcold:assay_temp_diff 5568
## trait_typesurvival:warm_coldcold:assay_temp_diff 5776
## trait_typebody_size:warm_coldwarm:assay_temp_diff 5865
## trait_typefecundity:warm_coldwarm:assay_temp_diff 5656
## trait_typesurvival:warm_coldwarm:assay_temp_diff 5976
## trait_typebody_size:warm_coldcold:var_lnCVR 5437
## trait_typefecundity:warm_coldcold:var_lnCVR 5453
## trait_typesurvival:warm_coldcold:var_lnCVR 3062
## trait_typebody_size:warm_coldwarm:var_lnCVR 5782
## trait_typefecundity:warm_coldwarm:var_lnCVR 5876
## trait_typesurvival:warm_coldwarm:var_lnCVR 5871
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnCVR_model_pub_bias_var, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(Parameter = gsub("trait_type([a-z_]+):warm_cold(cold|warm)", "\\1(\\2)",
Parameter), Parameter = gsub("trait_type([a-z_]+)\\(warm\\):assay_temp_diff",
"\\1(warm): assay_temp_diff", Parameter)) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size(cold) | -0.068 | -0.363 | 0.238 |
| fecundity(cold) | -0.062 | -0.628 | 0.510 |
| survival(cold) | 0.283 | -1.368 | 1.937 |
| body_size(warm) | 0.062 | -0.146 | 0.293 |
| fecundity(warm) | -0.048 | -0.255 | 0.168 |
| survival(warm) | 0.250 | -0.132 | 0.652 |
| body_size(cold):assay_temp_diff | -0.009 | -0.050 | 0.032 |
| fecundity(cold):assay_temp_diff | -0.151 | -0.230 | -0.071 |
| survival(cold):assay_temp_diff | 0.028 | -0.049 | 0.102 |
| body_size(warm):assay_temp_diff | 0.002 | -0.031 | 0.035 |
| fecundity(warm):assay_temp_diff | 0.017 | -0.013 | 0.048 |
| survival(warm):assay_temp_diff | -0.025 | -0.073 | 0.022 |
| body_size(cold):var_lnCVR | 0.286 | -0.630 | 1.222 |
| fecundity(cold):var_lnCVR | -0.606 | -1.744 | 0.543 |
| survival(cold):var_lnCVR | -0.393 | -3.976 | 3.152 |
| body_size(warm):var_lnCVR | -0.008 | -0.247 | 0.233 |
| fecundity(warm):var_lnCVR | 0.006 | -0.046 | 0.059 |
| survival(warm):var_lnCVR | -0.030 | -0.143 | 0.082 |
Data visualisation
# Generate predictions
emmeans_pub_bias_var <- as.data.frame(emmeans(
lnCVR_model_pub_bias_var,
specs = ~ var_lnCVR | trait_type * warm_cold,
at = list(var_lnCVR = seq(0, max(data$var_lnCVR), by = 1),
assay_temp_diff = 0))
) # Conditional effects on assay_temp_diff = 0
emmeans_pub_bias_var$trait_type <- factor(emmeans_pub_bias_var$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_pub_bias_var$warm_cold <- factor(emmeans_pub_bias_var$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = var_lnCVR,
y = lnCVR,
size = 1/sqrt(var_lnCVR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.1)) +
geom_ribbon(data = emmeans_pub_bias_var, # Shaded area for credible intervals
aes(x = var_lnCVR,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_pub_bias_var, # Predicted regression line
aes(y = emmean,
x = var_lnCVR,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -1.9, -2.85), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Sampling variance of lnCVR", y = "lnCVR", col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 25, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 22)) +
guides(fill = "none", size = "none") +
coord_cartesian(ylim = c(-3.5, 3.5), xlim = c(0, 6))
ggsave(file = "fig/lnCVR_pub_bias_var.png", width = 12, height = 7, dpi = 500)
Publication year
Model specification
# Model specification
formula <- bf(lnCVR ~ 0 + trait_type:warm_cold +
trait_type:warm_cold:assay_temp_diff +
trait_type:warm_cold:scale(pub_year) + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnCVR)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnCVR_model_pub_bias_year <- brm(formula,
family = gaussian(),
data = data,
data2 = list(phylo_matrix = phylo_matrix,
VCV_lnCVR = VCV_lnCVR),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnCVR_model_pub_bias_year, file = "RData/lnCVR_model_pub_bias_year.rds")
Model output
# Load model
lnCVR_model_pub_bias_year <- readRDS("RData/lnCVR_model_pub_bias_year.rds")
# Display model output
summary(lnCVR_model_pub_bias_year)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnCVR ~ 0 + trait_type:warm_cold + trait_type:warm_cold:assay_temp_diff + trait_type:warm_cold:scale(pub_year) + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnCVR)
## Data: data (Number of observations: 476)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 88)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.10 0.06 0.01 0.21 1.01 1181 2260
##
## ~obs (Number of levels: 476)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.40 0.04 0.32 0.48 1.00 2052 4112
##
## ~phylogeny (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.10 0.08 0.00 0.29 1.00 2668 3221
##
## ~ref (Number of levels: 44)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.07 0.05 0.00
## sd(trait_typefecundity) 0.10 0.08 0.00
## sd(trait_typesurvival) 0.45 0.17 0.11
## cor(trait_typebody_size,trait_typefecundity) 0.02 0.50 -0.87
## cor(trait_typebody_size,trait_typesurvival) -0.02 0.50 -0.89
## cor(trait_typefecundity,trait_typesurvival) -0.04 0.47 -0.87
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.20 1.00 3678 4215
## sd(trait_typefecundity) 0.30 1.00 1983 3752
## sd(trait_typesurvival) 0.79 1.00 2046 1896
## cor(trait_typebody_size,trait_typefecundity) 0.88 1.00 3875 5140
## cor(trait_typebody_size,trait_typesurvival) 0.87 1.00 1267 2796
## cor(trait_typefecundity,trait_typesurvival) 0.83 1.00 1635 4130
##
## ~species (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.07 0.05 0.00 0.20 1.00 3106 4276
##
## Regression Coefficients:
## Estimate Est.Error l-95% CI
## trait_typebody_size:warm_coldcold -0.06 0.13 -0.31
## trait_typefecundity:warm_coldcold -0.34 0.23 -0.78
## trait_typesurvival:warm_coldcold 0.16 0.40 -0.62
## trait_typebody_size:warm_coldwarm 0.06 0.11 -0.15
## trait_typefecundity:warm_coldwarm -0.04 0.11 -0.27
## trait_typesurvival:warm_coldwarm 0.31 0.18 -0.05
## trait_typebody_size:warm_coldcold:assay_temp_diff 0.00 0.02 -0.04
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.12 0.04 -0.21
## trait_typesurvival:warm_coldcold:assay_temp_diff 0.03 0.04 -0.05
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.00 0.02 -0.03
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.02 0.02 -0.01
## trait_typesurvival:warm_coldwarm:assay_temp_diff -0.03 0.02 -0.07
## trait_typebody_size:warm_coldcold:scalepub_year -0.08 0.08 -0.24
## trait_typefecundity:warm_coldcold:scalepub_year -0.12 0.16 -0.42
## trait_typesurvival:warm_coldcold:scalepub_year -0.02 0.26 -0.54
## trait_typebody_size:warm_coldwarm:scalepub_year 0.04 0.08 -0.12
## trait_typefecundity:warm_coldwarm:scalepub_year -0.04 0.10 -0.23
## trait_typesurvival:warm_coldwarm:scalepub_year -0.32 0.16 -0.63
## u-95% CI Rhat Bulk_ESS
## trait_typebody_size:warm_coldcold 0.21 1.00 2960
## trait_typefecundity:warm_coldcold 0.12 1.00 4139
## trait_typesurvival:warm_coldcold 0.93 1.00 4279
## trait_typebody_size:warm_coldwarm 0.28 1.00 3128
## trait_typefecundity:warm_coldwarm 0.18 1.00 3198
## trait_typesurvival:warm_coldwarm 0.67 1.00 3887
## trait_typebody_size:warm_coldcold:assay_temp_diff 0.04 1.00 2921
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.04 1.00 5240
## trait_typesurvival:warm_coldcold:assay_temp_diff 0.11 1.00 5224
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.04 1.00 4681
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.05 1.00 5046
## trait_typesurvival:warm_coldwarm:assay_temp_diff 0.02 1.00 5999
## trait_typebody_size:warm_coldcold:scalepub_year 0.07 1.00 3012
## trait_typefecundity:warm_coldcold:scalepub_year 0.20 1.00 4505
## trait_typesurvival:warm_coldcold:scalepub_year 0.51 1.00 4329
## trait_typebody_size:warm_coldwarm:scalepub_year 0.19 1.00 3249
## trait_typefecundity:warm_coldwarm:scalepub_year 0.16 1.00 4339
## trait_typesurvival:warm_coldwarm:scalepub_year 0.01 1.00 5332
## Tail_ESS
## trait_typebody_size:warm_coldcold 4255
## trait_typefecundity:warm_coldcold 5397
## trait_typesurvival:warm_coldcold 5604
## trait_typebody_size:warm_coldwarm 3724
## trait_typefecundity:warm_coldwarm 3977
## trait_typesurvival:warm_coldwarm 4586
## trait_typebody_size:warm_coldcold:assay_temp_diff 4436
## trait_typefecundity:warm_coldcold:assay_temp_diff 5979
## trait_typesurvival:warm_coldcold:assay_temp_diff 5692
## trait_typebody_size:warm_coldwarm:assay_temp_diff 5289
## trait_typefecundity:warm_coldwarm:assay_temp_diff 5577
## trait_typesurvival:warm_coldwarm:assay_temp_diff 5578
## trait_typebody_size:warm_coldcold:scalepub_year 4846
## trait_typefecundity:warm_coldcold:scalepub_year 4912
## trait_typesurvival:warm_coldcold:scalepub_year 5491
## trait_typebody_size:warm_coldwarm:scalepub_year 4728
## trait_typefecundity:warm_coldwarm:scalepub_year 5889
## trait_typesurvival:warm_coldwarm:scalepub_year 5242
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnCVR_model_pub_bias_year, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(
# Simplify `trait_type` and `warm_cold` interactions
Parameter = gsub("trait_type([a-z_]+):warm_cold(cold|warm)", "\\1(\\2)", Parameter),
# Adjust for assay_temp_diff
Parameter = gsub("trait_type([a-z_]+)\\((cold|warm)\\):assay_temp_diff", "\\1(\\2): assay_temp_diff", Parameter),
# Remove "scale" from pub_year
Parameter = gsub("scalepub_year", "pub_year", Parameter)
) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~ round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size(cold) | -0.060 | -0.308 | 0.212 |
| fecundity(cold) | -0.337 | -0.781 | 0.121 |
| survival(cold) | 0.158 | -0.621 | 0.935 |
| body_size(warm) | 0.058 | -0.151 | 0.280 |
| fecundity(warm) | -0.044 | -0.265 | 0.182 |
| survival(warm) | 0.306 | -0.052 | 0.672 |
| body_size(cold):assay_temp_diff | 0.001 | -0.043 | 0.044 |
| fecundity(cold):assay_temp_diff | -0.125 | -0.209 | -0.039 |
| survival(cold):assay_temp_diff | 0.030 | -0.049 | 0.108 |
| body_size(warm):assay_temp_diff | 0.002 | -0.030 | 0.037 |
| fecundity(warm):assay_temp_diff | 0.018 | -0.013 | 0.049 |
| survival(warm):assay_temp_diff | -0.026 | -0.074 | 0.021 |
| body_size(cold):pub_year | -0.083 | -0.242 | 0.073 |
| fecundity(cold):pub_year | -0.116 | -0.420 | 0.195 |
| survival(cold):pub_year | -0.015 | -0.536 | 0.508 |
| body_size(warm):pub_year | 0.038 | -0.119 | 0.193 |
| fecundity(warm):pub_year | -0.037 | -0.233 | 0.162 |
| survival(warm):pub_year | -0.315 | -0.629 | 0.013 |
Data visualisation
# Generate predictions
emmeans_pub_bias_year <- as.data.frame(emmeans(
lnCVR_model_pub_bias_year,
specs = ~ pub_year | trait_type * warm_cold,
at = list(pub_year = seq(min(data$pub_year), max(data$pub_year), by = 1),
assay_temp_diff = 0))
) # Conditional effects on assay_temp_diff = 0
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_pub_year = min(pub_year),
max_pub_year = max(pub_year)
)
emmeans_pub_bias_year$trait_type <- factor(emmeans_pub_bias_year$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_pub_bias_year$warm_cold <- factor(emmeans_pub_bias_year$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_pub_bias_year <- emmeans_pub_bias_year %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
pub_year >= min_pub_year,
pub_year <= max_pub_year
)
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = pub_year,
y = lnCVR,
size = 1/sqrt(pub_year),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_pub_bias_year, # Shaded area for credible intervals
aes(x = pub_year,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_pub_bias_year, # Predicted regression line
aes(y = emmean,
x = pub_year,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -1.9, -2.85), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Publication year", y = "lnCVR", col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 25, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 22)) +
guides(fill = "none", size = "none") +
coord_cartesian(ylim = c(-3, 3))
ggsave(file = "fig/lnCVR_pub_bias_year.png", width = 12, height = 7, dpi = 500)
Changes in trait variance (lnVR)
Egger’s regression
Model specification
# Model specification
formula <- bf(lnVR ~ 0 + trait_type:warm_cold +
trait_type:warm_cold:assay_temp_diff +
trait_type:warm_cold:var_lnVR + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnVR)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnVR_model_pub_bias_var <- brm(formula,
family = gaussian(),
data = data,
data2 = list(phylo_matrix = phylo_matrix,
VCV_lnVR = VCV_lnVR),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnVR_model_pub_bias_var, file = "RData/lnVR_model_pub_bias_var.rds")
Model output
# Load model
lnVR_model_pub_bias_var <- readRDS("RData/lnVR_model_pub_bias_var.rds")
# Display model output
summary(lnVR_model_pub_bias_var)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnVR ~ 0 + trait_type:warm_cold + trait_type:warm_cold:assay_temp_diff + trait_type:warm_cold:var_lnVR + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnVR)
## Data: data (Number of observations: 476)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 88)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.09 0.06 0.00 0.21 1.01 737 2137
##
## ~obs (Number of levels: 476)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.68 0.03 0.62 0.75 1.00 2971 5048
##
## ~phylogeny (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.09 0.07 0.00 0.28 1.00 2249 3881
##
## ~ref (Number of levels: 44)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.07 0.06 0.00
## sd(trait_typefecundity) 0.08 0.06 0.00
## sd(trait_typesurvival) 0.29 0.15 0.03
## cor(trait_typebody_size,trait_typefecundity) 0.02 0.50 -0.87
## cor(trait_typebody_size,trait_typesurvival) -0.04 0.50 -0.89
## cor(trait_typefecundity,trait_typesurvival) -0.04 0.49 -0.87
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.22 1.00 1908 2981
## sd(trait_typefecundity) 0.23 1.00 1728 3234
## sd(trait_typesurvival) 0.60 1.00 1204 1704
## cor(trait_typebody_size,trait_typefecundity) 0.88 1.00 3597 4697
## cor(trait_typebody_size,trait_typesurvival) 0.86 1.00 1327 3062
## cor(trait_typefecundity,trait_typesurvival) 0.86 1.00 1729 3702
##
## ~species (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.06 0.05 0.00 0.18 1.00 2962 4612
##
## Regression Coefficients:
## Estimate Est.Error l-95% CI
## trait_typebody_size:warm_coldcold -0.08 0.22 -0.51
## trait_typefecundity:warm_coldcold -0.26 0.33 -0.92
## trait_typesurvival:warm_coldcold 0.02 0.62 -1.22
## trait_typebody_size:warm_coldwarm 0.04 0.13 -0.21
## trait_typefecundity:warm_coldwarm -0.11 0.11 -0.31
## trait_typesurvival:warm_coldwarm 0.20 0.16 -0.10
## trait_typebody_size:warm_coldcold:assay_temp_diff -0.02 0.03 -0.08
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.19 0.05 -0.28
## trait_typesurvival:warm_coldcold:assay_temp_diff -0.01 0.04 -0.10
## trait_typebody_size:warm_coldwarm:assay_temp_diff -0.00 0.02 -0.04
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.02 0.02 -0.01
## trait_typesurvival:warm_coldwarm:assay_temp_diff -0.02 0.02 -0.07
## trait_typebody_size:warm_coldcold:var_lnVR 0.39 0.94 -1.41
## trait_typefecundity:warm_coldcold:var_lnVR -0.56 0.99 -2.48
## trait_typesurvival:warm_coldcold:var_lnVR -0.25 2.76 -5.59
## trait_typebody_size:warm_coldwarm:var_lnVR 0.02 0.19 -0.36
## trait_typefecundity:warm_coldwarm:var_lnVR 0.00 0.03 -0.05
## trait_typesurvival:warm_coldwarm:var_lnVR -0.01 0.06 -0.13
## u-95% CI Rhat Bulk_ESS
## trait_typebody_size:warm_coldcold 0.35 1.00 1542
## trait_typefecundity:warm_coldcold 0.39 1.00 2623
## trait_typesurvival:warm_coldcold 1.19 1.00 2402
## trait_typebody_size:warm_coldwarm 0.30 1.00 2159
## trait_typefecundity:warm_coldwarm 0.11 1.00 2628
## trait_typesurvival:warm_coldwarm 0.52 1.00 3041
## trait_typebody_size:warm_coldcold:assay_temp_diff 0.04 1.00 2084
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.09 1.00 3814
## trait_typesurvival:warm_coldcold:assay_temp_diff 0.07 1.00 3404
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.04 1.00 2198
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.05 1.00 2752
## trait_typesurvival:warm_coldwarm:assay_temp_diff 0.03 1.00 3352
## trait_typebody_size:warm_coldcold:var_lnVR 2.28 1.00 1942
## trait_typefecundity:warm_coldcold:var_lnVR 1.36 1.00 3364
## trait_typesurvival:warm_coldcold:var_lnVR 5.30 1.00 2714
## trait_typebody_size:warm_coldwarm:var_lnVR 0.40 1.00 5299
## trait_typefecundity:warm_coldwarm:var_lnVR 0.06 1.00 9284
## trait_typesurvival:warm_coldwarm:var_lnVR 0.11 1.00 12933
## Tail_ESS
## trait_typebody_size:warm_coldcold 2964
## trait_typefecundity:warm_coldcold 3808
## trait_typesurvival:warm_coldcold 2746
## trait_typebody_size:warm_coldwarm 3478
## trait_typefecundity:warm_coldwarm 3353
## trait_typesurvival:warm_coldwarm 4432
## trait_typebody_size:warm_coldcold:assay_temp_diff 3717
## trait_typefecundity:warm_coldcold:assay_temp_diff 5176
## trait_typesurvival:warm_coldcold:assay_temp_diff 4891
## trait_typebody_size:warm_coldwarm:assay_temp_diff 3858
## trait_typefecundity:warm_coldwarm:assay_temp_diff 4295
## trait_typesurvival:warm_coldwarm:assay_temp_diff 4928
## trait_typebody_size:warm_coldcold:var_lnVR 3704
## trait_typefecundity:warm_coldcold:var_lnVR 4765
## trait_typesurvival:warm_coldcold:var_lnVR 3194
## trait_typebody_size:warm_coldwarm:var_lnVR 5165
## trait_typefecundity:warm_coldwarm:var_lnVR 6282
## trait_typesurvival:warm_coldwarm:var_lnVR 6344
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnVR_model_pub_bias_var, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(Parameter = gsub("trait_type([a-z_]+):warm_cold(cold|warm)", "\\1(\\2)",
Parameter), Parameter = gsub("trait_type([a-z_]+)\\(warm\\):assay_temp_diff",
"\\1(warm): assay_temp_diff", Parameter)) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size(cold) | -0.077 | -0.506 | 0.353 |
| fecundity(cold) | -0.263 | -0.921 | 0.391 |
| survival(cold) | 0.023 | -1.224 | 1.190 |
| body_size(warm) | 0.041 | -0.206 | 0.296 |
| fecundity(warm) | -0.106 | -0.313 | 0.115 |
| survival(warm) | 0.202 | -0.099 | 0.523 |
| body_size(cold):assay_temp_diff | -0.019 | -0.076 | 0.039 |
| fecundity(cold):assay_temp_diff | -0.185 | -0.283 | -0.091 |
| survival(cold):assay_temp_diff | -0.014 | -0.103 | 0.074 |
| body_size(warm):assay_temp_diff | -0.001 | -0.043 | 0.039 |
| fecundity(warm):assay_temp_diff | 0.019 | -0.011 | 0.049 |
| survival(warm):assay_temp_diff | -0.022 | -0.070 | 0.026 |
| body_size(cold):var_lnVR | 0.395 | -1.410 | 2.278 |
| fecundity(cold):var_lnVR | -0.559 | -2.477 | 1.364 |
| survival(cold):var_lnVR | -0.249 | -5.589 | 5.301 |
| body_size(warm):var_lnVR | 0.016 | -0.365 | 0.396 |
| fecundity(warm):var_lnVR | 0.004 | -0.053 | 0.061 |
| survival(warm):var_lnVR | -0.011 | -0.126 | 0.107 |
Data visualisation
# Generate predictions
emmeans_pub_bias_var <- as.data.frame(emmeans(
lnVR_model_pub_bias_var,
specs = ~ var_lnVR | trait_type * warm_cold,
at = list(var_lnVR = seq(0, max(data$var_lnVR), by = 1),
assay_temp_diff = 0))
) # Conditional effects on assay_temp_diff = 0
emmeans_pub_bias_var$trait_type <- factor(emmeans_pub_bias_var$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_pub_bias_var$warm_cold <- factor(emmeans_pub_bias_var$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = var_lnVR,
y = lnVR,
size = 1/sqrt(var_lnVR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_pub_bias_var, # Shaded area for credible intervals
aes(x = var_lnVR,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_pub_bias_var, # Predicted regression line
aes(y = emmean,
x = var_lnVR,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -2.3, -3.25), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Sampling variance of lnVR", y = "lnVR", col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 25, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 22)) +
guides(fill = "none", size = "none")+
coord_cartesian(ylim = c(-3.5, 3.5), xlim = c(0, 6))
ggsave(file = "fig/lnVR_pub_bias_var.png", width = 12, height = 7, dpi = 500)
Publication year
Model specification
# Model specification
formula <- bf(lnVR ~ 0 + trait_type:warm_cold +
trait_type:warm_cold:assay_temp_diff +
trait_type:warm_cold:scale(pub_year) + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnVR)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnVR_model_pub_bias_year <- brm(formula,
family = gaussian(),
data = data,
data2 = list(phylo_matrix = phylo_matrix,
VCV_lnVR = VCV_lnVR),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnVR_model_pub_bias_year, file = "RData/lnVR_model_pub_bias_year.rds")
Model output
# Load model
lnVR_model_pub_bias_year <- readRDS("RData/lnVR_model_pub_bias_year.rds")
# Display model output
summary(lnVR_model_pub_bias_year)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnVR ~ 0 + trait_type:warm_cold + trait_type:warm_cold:assay_temp_diff + trait_type:warm_cold:scale(pub_year) + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnVR)
## Data: data (Number of observations: 476)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 88)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.09 0.06 0.00 0.22 1.01 864 2034
##
## ~obs (Number of levels: 476)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.68 0.03 0.62 0.75 1.00 3134 5430
##
## ~phylogeny (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.11 0.08 0.00 0.30 1.00 2255 3663
##
## ~ref (Number of levels: 44)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.08 0.06 0.00
## sd(trait_typefecundity) 0.09 0.07 0.00
## sd(trait_typesurvival) 0.20 0.13 0.01
## cor(trait_typebody_size,trait_typefecundity) 0.02 0.50 -0.87
## cor(trait_typebody_size,trait_typesurvival) -0.02 0.51 -0.88
## cor(trait_typefecundity,trait_typesurvival) -0.00 0.49 -0.88
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.23 1.00 2885 4406
## sd(trait_typefecundity) 0.24 1.00 2260 3630
## sd(trait_typesurvival) 0.50 1.00 1241 3747
## cor(trait_typebody_size,trait_typefecundity) 0.88 1.00 4014 4855
## cor(trait_typebody_size,trait_typesurvival) 0.87 1.00 2156 4340
## cor(trait_typefecundity,trait_typesurvival) 0.87 1.00 3278 4983
##
## ~species (Number of levels: 25)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.06 0.05 0.00 0.19 1.00 2864 4175
##
## Regression Coefficients:
## Estimate Est.Error l-95% CI
## trait_typebody_size:warm_coldcold -0.04 0.16 -0.35
## trait_typefecundity:warm_coldcold -0.26 0.25 -0.76
## trait_typesurvival:warm_coldcold 0.05 0.41 -0.77
## trait_typebody_size:warm_coldwarm 0.05 0.13 -0.19
## trait_typefecundity:warm_coldwarm -0.12 0.12 -0.37
## trait_typesurvival:warm_coldwarm 0.27 0.16 -0.04
## trait_typebody_size:warm_coldcold:assay_temp_diff -0.01 0.03 -0.07
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.20 0.05 -0.31
## trait_typesurvival:warm_coldcold:assay_temp_diff -0.01 0.05 -0.11
## trait_typebody_size:warm_coldwarm:assay_temp_diff -0.00 0.02 -0.04
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.02 0.02 -0.01
## trait_typesurvival:warm_coldwarm:assay_temp_diff -0.02 0.02 -0.07
## trait_typebody_size:warm_coldcold:scalepub_year -0.09 0.11 -0.30
## trait_typefecundity:warm_coldcold:scalepub_year 0.21 0.19 -0.17
## trait_typesurvival:warm_coldcold:scalepub_year 0.03 0.26 -0.47
## trait_typebody_size:warm_coldwarm:scalepub_year 0.04 0.11 -0.17
## trait_typefecundity:warm_coldwarm:scalepub_year 0.05 0.11 -0.17
## trait_typesurvival:warm_coldwarm:scalepub_year -0.26 0.12 -0.50
## u-95% CI Rhat Bulk_ESS
## trait_typebody_size:warm_coldcold 0.28 1.00 2854
## trait_typefecundity:warm_coldcold 0.24 1.00 3882
## trait_typesurvival:warm_coldcold 0.86 1.00 4101
## trait_typebody_size:warm_coldwarm 0.30 1.00 2516
## trait_typefecundity:warm_coldwarm 0.12 1.00 2638
## trait_typesurvival:warm_coldwarm 0.59 1.00 2735
## trait_typebody_size:warm_coldcold:assay_temp_diff 0.05 1.00 2677
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.10 1.00 4454
## trait_typesurvival:warm_coldcold:assay_temp_diff 0.08 1.00 3886
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.04 1.00 3382
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.05 1.00 3182
## trait_typesurvival:warm_coldwarm:assay_temp_diff 0.02 1.00 3830
## trait_typebody_size:warm_coldcold:scalepub_year 0.13 1.00 2210
## trait_typefecundity:warm_coldcold:scalepub_year 0.58 1.00 4224
## trait_typesurvival:warm_coldcold:scalepub_year 0.54 1.00 3901
## trait_typebody_size:warm_coldwarm:scalepub_year 0.26 1.00 2626
## trait_typefecundity:warm_coldwarm:scalepub_year 0.27 1.00 3255
## trait_typesurvival:warm_coldwarm:scalepub_year -0.01 1.00 5250
## Tail_ESS
## trait_typebody_size:warm_coldcold 3688
## trait_typefecundity:warm_coldcold 4512
## trait_typesurvival:warm_coldcold 5122
## trait_typebody_size:warm_coldwarm 3116
## trait_typefecundity:warm_coldwarm 3368
## trait_typesurvival:warm_coldwarm 2998
## trait_typebody_size:warm_coldcold:assay_temp_diff 3881
## trait_typefecundity:warm_coldcold:assay_temp_diff 5516
## trait_typesurvival:warm_coldcold:assay_temp_diff 5024
## trait_typebody_size:warm_coldwarm:assay_temp_diff 4843
## trait_typefecundity:warm_coldwarm:assay_temp_diff 4842
## trait_typesurvival:warm_coldwarm:assay_temp_diff 4994
## trait_typebody_size:warm_coldcold:scalepub_year 3645
## trait_typefecundity:warm_coldcold:scalepub_year 5647
## trait_typesurvival:warm_coldcold:scalepub_year 5028
## trait_typebody_size:warm_coldwarm:scalepub_year 3903
## trait_typefecundity:warm_coldwarm:scalepub_year 4683
## trait_typesurvival:warm_coldwarm:scalepub_year 5690
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnVR_model_pub_bias_year, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(
# Simplify `trait_type` and `warm_cold` interactions
Parameter = gsub("trait_type([a-z_]+):warm_cold(cold|warm)", "\\1(\\2)", Parameter),
# Adjust for assay_temp_diff
Parameter = gsub("trait_type([a-z_]+)\\((cold|warm)\\):assay_temp_diff", "\\1(\\2): assay_temp_diff", Parameter),
# Remove "scale" from pub_year
Parameter = gsub("scalepub_year", "pub_year", Parameter)
) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~ round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size(cold) | -0.044 | -0.349 | 0.276 |
| fecundity(cold) | -0.259 | -0.755 | 0.241 |
| survival(cold) | 0.049 | -0.766 | 0.858 |
| body_size(warm) | 0.045 | -0.190 | 0.302 |
| fecundity(warm) | -0.123 | -0.365 | 0.119 |
| survival(warm) | 0.266 | -0.037 | 0.594 |
| body_size(cold):assay_temp_diff | -0.007 | -0.066 | 0.053 |
| fecundity(cold):assay_temp_diff | -0.203 | -0.309 | -0.101 |
| survival(cold):assay_temp_diff | -0.015 | -0.107 | 0.079 |
| body_size(warm):assay_temp_diff | -0.002 | -0.041 | 0.039 |
| fecundity(warm):assay_temp_diff | 0.020 | -0.010 | 0.050 |
| survival(warm):assay_temp_diff | -0.024 | -0.071 | 0.022 |
| body_size(cold):pub_year | -0.089 | -0.303 | 0.131 |
| fecundity(cold):pub_year | 0.207 | -0.169 | 0.583 |
| survival(cold):pub_year | 0.035 | -0.468 | 0.539 |
| body_size(warm):pub_year | 0.042 | -0.172 | 0.256 |
| fecundity(warm):pub_year | 0.050 | -0.165 | 0.266 |
| survival(warm):pub_year | -0.258 | -0.496 | -0.012 |
Data visualisation
# Generate predictions
emmeans_pub_bias_year <- as.data.frame(emmeans(
lnVR_model_pub_bias_year,
specs = ~ pub_year | trait_type * warm_cold,
at = list(pub_year = seq(min(data$pub_year), max(data$pub_year), by = 1),
assay_temp_diff = 0))
) # Conditional effects on assay_temp_diff = 0
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_pub_year = min(pub_year),
max_pub_year = max(pub_year)
)
emmeans_pub_bias_year$trait_type <- factor(emmeans_pub_bias_year$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_pub_bias_year$warm_cold <- factor(emmeans_pub_bias_year$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_pub_bias_year <- emmeans_pub_bias_year %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
pub_year >= min_pub_year,
pub_year <= max_pub_year
)
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = pub_year,
y = lnVR,
size = 1/sqrt(pub_year),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_pub_bias_year, # Shaded area for credible intervals
aes(x = pub_year,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_pub_bias_year, # Predicted regression line
aes(y = emmean,
x = pub_year,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -2.3, -3.25), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Sampling variance of lnVR", y = "lnVR", col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 25, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 22)) +
guides(fill = "none", size = "none") +
coord_cartesian(ylim = c(-3.5, 3.5))
ggsave(file = "fig/lnVR_pub_bias_year.png", width = 12, height = 7, dpi = 500)
Sensitivity analyses
Note that, although mentioned in our pre-registration, we did not compare estimates from peer-reviewed publications and academic theses. This is because only a single included study was a thesis.
Prepare dataset for subset analyses
# Filter to only warm temperatures
data_warm <- filter(data, warm_cold == "Warm")
# Drop tips that are not in the warm dataset
tips_to_drop <- setdiff(phylo_tree$tip.label, data_warm$phylogeny)
phylo_tree_warm <- drop.tip(phylo_tree, tips_to_drop)
# Compute phylogenetic correlation matrix
phylo_matrix_warm <- vcv(phylo_tree_warm, cor = T) # The vcv function returns a variance-covariance matrix
# Convert tibble to data frame
data_warm <- as.data.frame(data_warm)
# Calculate VCV matrix for all three responses
VCV_lnRR_warm <- vcalc(vi = var_lnRR, cluster = shared_control_ID, rho = 0.5, obs = obs,
data = data_warm)
VCV_lnCVR_warm <- vcalc(vi = var_lnCVR, cluster = shared_control_ID, rho = 0.5, obs = obs,
data = data_warm)
VCV_lnVR_warm <- vcalc(vi = var_lnVR, cluster = shared_control_ID, rho = 0.5, obs = obs,
data = data_warm)
Changes in mean responses (lnRR)
Constant vs. increasing temperatures
Model specification
# Model specification
formula <- bf(lnRR ~ 0 + trait_type:constant_increasing + trait_type:constant_increasing:assay_temp_diff + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix_warm)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnRR_warm)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnRR_model_constant_increasing <- brm(formula,
family = gaussian(),
data = data_warm,
data2 = list(phylo_matrix_warm = phylo_matrix_warm,
VCV_lnRR_warm = VCV_lnRR_warm),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnRR_model_constant_increasing, file = "RData/lnRR_model_constant_increasing.rds")
Model output
# Load model
lnRR_model_constant_increasing <- readRDS("RData/lnRR_model_constant_increasing.rds")
# Display model output
summary(lnRR_model_constant_increasing)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnRR ~ 0 + trait_type:constant_increasing + trait_type:constant_increasing:assay_temp_diff + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix_warm)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnRR_warm)
## Data: data_warm (Number of observations: 381)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 81)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.02 0.01 0.00 0.04 1.00 1181 1279
##
## ~obs (Number of levels: 381)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.05 0.01 0.04 0.06 1.01 1304 2710
##
## ~phylogeny (Number of levels: 23)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.02 0.02 0.00 0.06 1.00 2344 3808
##
## ~ref (Number of levels: 38)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.02 0.01 0.00
## sd(trait_typefecundity) 0.22 0.04 0.15
## sd(trait_typesurvival) 0.14 0.03 0.09
## cor(trait_typebody_size,trait_typefecundity) -0.19 0.41 -0.88
## cor(trait_typebody_size,trait_typesurvival) 0.31 0.42 -0.66
## cor(trait_typefecundity,trait_typesurvival) -0.34 0.47 -0.95
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.04 1.00 1417 2639
## sd(trait_typefecundity) 0.31 1.00 3306 4983
## sd(trait_typesurvival) 0.22 1.00 4833 5595
## cor(trait_typebody_size,trait_typefecundity) 0.68 1.01 488 886
## cor(trait_typebody_size,trait_typesurvival) 0.93 1.00 1073 1756
## cor(trait_typefecundity,trait_typesurvival) 0.70 1.01 1070 2328
##
## ~species (Number of levels: 23)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.02 0.01 0.00 0.04 1.00 2742 4545
##
## Regression Coefficients:
## Estimate
## trait_typebody_size:constant_increasingconstant -0.01
## trait_typefecundity:constant_increasingconstant -0.05
## trait_typesurvival:constant_increasingconstant 0.02
## trait_typebody_size:constant_increasingincreasing 0.03
## trait_typefecundity:constant_increasingincreasing -0.04
## trait_typesurvival:constant_increasingincreasing 0.00
## trait_typebody_size:constant_increasingconstant:assay_temp_diff -0.00
## trait_typefecundity:constant_increasingconstant:assay_temp_diff 0.01
## trait_typesurvival:constant_increasingconstant:assay_temp_diff 0.01
## trait_typebody_size:constant_increasingincreasing:assay_temp_diff 0.00
## trait_typefecundity:constant_increasingincreasing:assay_temp_diff -0.00
## trait_typesurvival:constant_increasingincreasing:assay_temp_diff -0.01
## Est.Error
## trait_typebody_size:constant_increasingconstant 0.02
## trait_typefecundity:constant_increasingconstant 0.06
## trait_typesurvival:constant_increasingconstant 0.05
## trait_typebody_size:constant_increasingincreasing 0.04
## trait_typefecundity:constant_increasingincreasing 0.11
## trait_typesurvival:constant_increasingincreasing 0.08
## trait_typebody_size:constant_increasingconstant:assay_temp_diff 0.00
## trait_typefecundity:constant_increasingconstant:assay_temp_diff 0.00
## trait_typesurvival:constant_increasingconstant:assay_temp_diff 0.00
## trait_typebody_size:constant_increasingincreasing:assay_temp_diff 0.01
## trait_typefecundity:constant_increasingincreasing:assay_temp_diff 0.01
## trait_typesurvival:constant_increasingincreasing:assay_temp_diff 0.01
## l-95% CI
## trait_typebody_size:constant_increasingconstant -0.05
## trait_typefecundity:constant_increasingconstant -0.16
## trait_typesurvival:constant_increasingconstant -0.07
## trait_typebody_size:constant_increasingincreasing -0.06
## trait_typefecundity:constant_increasingincreasing -0.25
## trait_typesurvival:constant_increasingincreasing -0.16
## trait_typebody_size:constant_increasingconstant:assay_temp_diff -0.00
## trait_typefecundity:constant_increasingconstant:assay_temp_diff -0.00
## trait_typesurvival:constant_increasingconstant:assay_temp_diff -0.00
## trait_typebody_size:constant_increasingincreasing:assay_temp_diff -0.01
## trait_typefecundity:constant_increasingincreasing:assay_temp_diff -0.02
## trait_typesurvival:constant_increasingincreasing:assay_temp_diff -0.03
## u-95% CI Rhat
## trait_typebody_size:constant_increasingconstant 0.04 1.00
## trait_typefecundity:constant_increasingconstant 0.06 1.00
## trait_typesurvival:constant_increasingconstant 0.11 1.00
## trait_typebody_size:constant_increasingincreasing 0.10 1.00
## trait_typefecundity:constant_increasingincreasing 0.17 1.00
## trait_typesurvival:constant_increasingincreasing 0.17 1.00
## trait_typebody_size:constant_increasingconstant:assay_temp_diff 0.00 1.00
## trait_typefecundity:constant_increasingconstant:assay_temp_diff 0.01 1.00
## trait_typesurvival:constant_increasingconstant:assay_temp_diff 0.01 1.00
## trait_typebody_size:constant_increasingincreasing:assay_temp_diff 0.02 1.00
## trait_typefecundity:constant_increasingincreasing:assay_temp_diff 0.01 1.00
## trait_typesurvival:constant_increasingincreasing:assay_temp_diff 0.01 1.00
## Bulk_ESS
## trait_typebody_size:constant_increasingconstant 3596
## trait_typefecundity:constant_increasingconstant 3418
## trait_typesurvival:constant_increasingconstant 4395
## trait_typebody_size:constant_increasingincreasing 3532
## trait_typefecundity:constant_increasingincreasing 4370
## trait_typesurvival:constant_increasingincreasing 4673
## trait_typebody_size:constant_increasingconstant:assay_temp_diff 4760
## trait_typefecundity:constant_increasingconstant:assay_temp_diff 3980
## trait_typesurvival:constant_increasingconstant:assay_temp_diff 5244
## trait_typebody_size:constant_increasingincreasing:assay_temp_diff 5161
## trait_typefecundity:constant_increasingincreasing:assay_temp_diff 8804
## trait_typesurvival:constant_increasingincreasing:assay_temp_diff 6732
## Tail_ESS
## trait_typebody_size:constant_increasingconstant 3668
## trait_typefecundity:constant_increasingconstant 4083
## trait_typesurvival:constant_increasingconstant 5340
## trait_typebody_size:constant_increasingincreasing 3649
## trait_typefecundity:constant_increasingincreasing 5079
## trait_typesurvival:constant_increasingincreasing 5498
## trait_typebody_size:constant_increasingconstant:assay_temp_diff 5581
## trait_typefecundity:constant_increasingconstant:assay_temp_diff 6030
## trait_typesurvival:constant_increasingconstant:assay_temp_diff 5166
## trait_typebody_size:constant_increasingincreasing:assay_temp_diff 5335
## trait_typefecundity:constant_increasingincreasing:assay_temp_diff 6882
## trait_typesurvival:constant_increasingincreasing:assay_temp_diff 5784
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnRR_model_constant_increasing, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(
# Simplify `trait_type` and `constant_increasing` interactions
Parameter = gsub("trait_type([a-z_]+):constant_increasing(constant|increasing)", "\\1(\\2)", Parameter),
# Adjust for assay_temp_diff
Parameter = gsub("trait_type([a-z_]+)\\((constant|increasing)\\):assay_temp_diff", "\\1(\\2): assay_temp_diff", Parameter)
) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~ round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size(constant) | -0.005 | -0.047 | 0.037 |
| fecundity(constant) | -0.050 | -0.160 | 0.059 |
| survival(constant) | 0.015 | -0.074 | 0.106 |
| body_size(increasing) | 0.026 | -0.057 | 0.102 |
| fecundity(increasing) | -0.042 | -0.251 | 0.170 |
| survival(increasing) | 0.003 | -0.162 | 0.168 |
| body_size(constant):assay_temp_diff | -0.001 | -0.005 | 0.002 |
| fecundity(constant):assay_temp_diff | 0.006 | -0.002 | 0.015 |
| survival(constant):assay_temp_diff | 0.005 | -0.001 | 0.012 |
| body_size(increasing):assay_temp_diff | 0.003 | -0.011 | 0.016 |
| fecundity(increasing):assay_temp_diff | -0.003 | -0.019 | 0.013 |
| survival(increasing):assay_temp_diff | -0.007 | -0.026 | 0.012 |
Contrasts
# Generate predictions
emms <- emmeans(lnRR_model_constant_increasing, specs = ~constant_increasing | trait_type,
at = list(assay_temp_diff = 0))
# Generate contrasts
contrast(emms, method = "pairwise")
## trait_type = body_size:
## contrast estimate lower.HPD upper.HPD
## constant - increasing -0.03191 -0.106 0.0427
##
## trait_type = fecundity:
## contrast estimate lower.HPD upper.HPD
## constant - increasing -0.00587 -0.236 0.2302
##
## trait_type = survival:
## contrast estimate lower.HPD upper.HPD
## constant - increasing 0.01147 -0.160 0.2028
##
## Point estimate displayed: median
## HPD interval probability: 0.95
Data visualisation
# Generate predictions
emmeans_constant_increasing <- as.data.frame(emmeans(lnRR_model_constant_increasing,
specs = ~ constant_increasing | trait_type,
at = list(assay_temp_diff = 0)))
emmeans_constant_increasing$trait_type <- factor(emmeans_constant_increasing$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_constant_increasing$constant_increasing <- factor(emmeans_constant_increasing$constant_increasing,
levels = c("constant", "increasing"),
labels = c("Constant", "Increasing"))
data_warm$constant_increasing <- factor(data_warm$constant_increasing,
levels = c("constant", "increasing"),
labels = c("Constant", "Increasing"))
# Calculate sample sizes and study counts
sample_sizes_warm <- data_warm %>%
group_by(trait_type, constant_increasing) %>%
summarise(
estimates = n(),
studies = n_distinct(ref)
)
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75,
lwd=1) +
geom_quasirandom(data = data_warm, # Plot effect sizes, scaled by precision
aes(x = trait_type:constant_increasing,
y = lnRR,
fill = trait_type:constant_increasing,
size = 1/sqrt(var_lnRR)),
color = "black",
shape = 21,
width = 0.25,
alpha = 0.45) +
geom_errorbar(data = emmeans_constant_increasing, # Plot the point estimates in white for background
aes(x = trait_type:constant_increasing,
ymin = lower.HPD,
ymax = upper.HPD),
color="white",
size = 2.5,
width = 0.17) +
geom_point(data = emmeans_constant_increasing, # Plot the credible intervals in white for background
aes(x = trait_type:constant_increasing,
y = emmean),
shape = 21,
size = 4,
stroke = 2,
color="white",
fill = "white") +
geom_errorbar(data = emmeans_constant_increasing, # Plot the point estimates
aes(x = trait_type:constant_increasing,
ymin = lower.HPD,
ymax = upper.HPD),
color="black",
size = 2,
width = 0.15) +
geom_point(data = emmeans_constant_increasing, # Plot the credible intervals
aes(x = trait_type:constant_increasing,
y = emmean),
shape = 21,
size = 3.5,
stroke = 2,
color="black",
fill = "white") +
geom_text(data = sample_sizes_warm, # Sample size annotations
aes(x = trait_type:constant_increasing,
y = 1.5,
label = paste0("k = ", estimates, " (", studies, ")"),
color = constant_increasing),
hjust = 1, size = 5, show.legend = FALSE) +
theme_bw() + # Customize the plot
labs(y = "lnRR", x = "") +
scale_size_continuous(range = c(2, 8))+
scale_fill_manual(values = c("#8E77FF", "#FF6040", "#8E77FF", "#FF6040", "#8E77FF", "#FF6040"))+
scale_color_manual(values = c("Constant" = "#8E77FF", "Increasing" = "#FF6040")) + # Text colour
theme(text = element_text(size = 20, color = "black"),
legend.title = ggplot2::element_text(size = 16),
legend.text = ggplot2::element_text(size = 14),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5)) +
guides(fill = "none", size = "none")+
coord_flip() +
ylim(-1.5, 1.5)
ggsave(file = "fig/lnRR_constant_increasing.png", width = 12, height = 7, dpi = 500)
Leave-one-out analysis
Model specification
# Get a list of study IDs for the LOO
study_ids <- unique(data$ref)
num_studies <- length(study_ids)
# Initialize a list to store the results
loo_results <- vector("list", num_studies)
names(loo_results) <- study_ids
# Set up parallel processing (16 models at a time)
plan(multisession, workers = 16)
# Model specification
formula <- bf(lnRR ~ trait_type -1 + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnRR)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Function to fit the model after iteratively removing one study
leave_one_out <- function(study_id) {
# Subset the data
data_subset <- subset(data, ref != study_id)
# Recalculate VCV matrix
VCV_lnRR_subset <- vcalc(
vi = var_lnRR,
cluster = shared_control_ID,
rho = 0.5,
obs = obs,
data = data_subset
)
# Fit the model
model_subset <- brm(
formula,
family = gaussian(),
data = data_subset,
data2 = list(
phylo_matrix = phylo_matrix,
VCV_lnRR = VCV_lnRR_subset
),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 2000, # Reduce to 2000 iterations to reduce computational demands
warmup = 1000,
chains = 2, # Only two chains to reduce computational demands
cores = 1, # Set cores to 1 inside the function
seed = 123
)
# Extract the fixed effects
fixed_effects <- fixef(model_subset)
# Return the fixed effects
return(list(study_id = study_id, fixed_effects = fixed_effects))
}
# Run the function in parallel over the studies
loo_results <- future_lapply(study_ids, leave_one_out)
# Save results
saveRDS(loo_results, file = "RData/lnRR_LOO_results.rds")
Model summary
# Load the list of all model outputs
loo_lnRR <- readRDS(file = "RData/lnRR_LOO_results.rds")
# Extract data on fixed effects from each model
loo_results_lnRR <- do.call(rbind, lapply(loo_lnRR, function(x) {
# Extract study_id and fixed_effects data
study_id <- x$study_id
fixed_effects <- x$fixed_effects
# Convert row names (trait_type) to a column, remove prefix, and add
# study_id
fixed_effects <- data.frame(trait_type = sub("trait_type", "", rownames(fixed_effects)),
fixed_effects)
fixed_effects$study_id <- study_id
# Remove row names
rownames(fixed_effects) <- NULL
return(fixed_effects)
}))
loo_results_lnRR$trait_type <- factor(loo_results_lnRR$trait_type, levels = c("body_size",
"fecundity", "survival"), labels = c("Body size", "Fecundity", "Survival"))
# Summarise the data
loo_results_lnRR %>%
group_by(trait_type) %>%
summarise(Estimate = mean(Estimate), SE = mean(Est.Error), lower_CI = mean(Q2.5),
upper_CI = mean(Q97.5)) # Mean across all models
## # A tibble: 3 × 5
## trait_type Estimate SE lower_CI upper_CI
## <fct> <dbl> <dbl> <dbl> <dbl>
## 1 Body size -0.00139 0.0206 -0.0436 0.0396
## 2 Fecundity -0.0220 0.0438 -0.109 0.0635
## 3 Survival 0.0220 0.0337 -0.0435 0.0906
Data visualisation
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75,
lwd=1) +
geom_errorbar(data = loo_results_lnRR, # Plot the credible intervals in white for background
aes(x = study_id,
y = Estimate,
ymin = Q2.5,
ymax = Q97.5,
color = trait_type),
shape = 21,
size = 1,
width = 0.3) +
geom_point(data = loo_results_lnRR,
aes(x = study_id,
y = Estimate,
fill = trait_type),
shape = 21,
size = 4,
stroke = 1,
color="black") +
theme_bw() + # Customize the plot
labs(y = "lnRR", x = "") +
scale_fill_manual(values = c("#73B706", "#06A2BA", "#B90674"))+
scale_color_manual(values = c("#73B706", "#06A2BA", "#B90674")) +
theme(text = element_text(size = 20, color = "black"),
legend.title = ggplot2::element_text(size = 16),
legend.text = ggplot2::element_text(size = 14),
axis.text.y = ggplot2::element_text(size = 10,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 20)) +
guides(fill = "none", color = "none")+
coord_flip()+
xlim(-0.15, 0.15) +
facet_wrap(~trait_type, ncol = 3)+
scale_x_discrete(limits = rev(levels(as.factor(loo_results_lnRR$study_id)))) # Alphabetical order
ggsave(file = "fig/lnRR_LOO.png", width = 12, height = 12, dpi = 500)
Unusual study designs
Model specification
# Discard observations flagged because the number of generations of selection was unclear, as this would only affect the results of meta-regressions
data <- mutate(data, minor_concerns2 = ifelse(minor_concerns == "gen_selection approximate" |
minor_concerns == "gen_selection underestimated",
NA, minor_concerns))
# Filter to data without sources of procedural concerns.
data_concerns <- filter(data, is.na(major_concerns)==T & is.na(minor_concerns2)==T)
# Drop tips that are not in the warm dataset
tips_to_drop <- setdiff(phylo_tree$tip.label, data_concerns$phylogeny)
phylo_tree_concerns <- drop.tip(phylo_tree, tips_to_drop)
# Compute phylogenetic correlation matrix
phylo_matrix_concerns <- vcv(phylo_tree_concerns, cor = T) # The vcv function returns a variance-covariance matrix
# Convert tibble to data frame
data_concerns <- as.data.frame(data_concerns)
# Calculate VCV matrix
VCV_lnRR_concerns <- vcalc(vi = var_lnRR,
cluster = shared_control_ID,
rho = 0.5,
obs = obs,
data = data_concerns)
# Model specification
formula <- bf(lnRR ~ 0 + trait_type:warm_cold + trait_type:warm_cold:assay_temp_diff + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix_concerns)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnRR_concerns)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnRR_model_concerns <- brm(formula,
family = gaussian(),
data = data_concerns,
data2 = list(phylo_matrix_concerns = phylo_matrix_concerns,
VCV_lnRR_concerns = VCV_lnRR_concerns),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnRR_model_concerns, file = "RData/lnRR_model_concerns.rds")
Model summary
# Load model
lnRR_model_concerns <- readRDS("RData/lnRR_model_concerns.rds")
# Display model output
summary(lnRR_model_concerns)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnRR ~ 0 + trait_type:warm_cold + trait_type:warm_cold:assay_temp_diff + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix_concerns)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnRR_concerns)
## Data: data_concerns (Number of observations: 321)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 72)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.02 0.01 0.00 0.04 1.00 1174 1392
##
## ~obs (Number of levels: 321)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.04 0.00 0.03 0.04 1.00 2135 3894
##
## ~phylogeny (Number of levels: 22)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.03 0.02 0.00 0.07 1.00 2060 2542
##
## ~ref (Number of levels: 33)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.02 0.01 0.00
## sd(trait_typefecundity) 0.25 0.06 0.16
## sd(trait_typesurvival) 0.12 0.03 0.07
## cor(trait_typebody_size,trait_typefecundity) -0.05 0.45 -0.88
## cor(trait_typebody_size,trait_typesurvival) 0.16 0.44 -0.77
## cor(trait_typefecundity,trait_typesurvival) -0.10 0.59 -0.94
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.04 1.00 1269 2073
## sd(trait_typefecundity) 0.38 1.00 2829 4738
## sd(trait_typesurvival) 0.20 1.00 4415 5524
## cor(trait_typebody_size,trait_typefecundity) 0.81 1.01 494 944
## cor(trait_typebody_size,trait_typesurvival) 0.90 1.00 1371 2002
## cor(trait_typefecundity,trait_typesurvival) 0.89 1.00 984 3114
##
## ~species (Number of levels: 22)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.02 0.01 0.00 0.05 1.00 2688 3110
##
## Regression Coefficients:
## Estimate Est.Error l-95% CI
## trait_typebody_size:warm_coldcold 0.04 0.02 -0.01
## trait_typefecundity:warm_coldcold -0.41 0.11 -0.62
## trait_typesurvival:warm_coldcold 0.06 0.07 -0.08
## trait_typebody_size:warm_coldwarm -0.01 0.02 -0.05
## trait_typefecundity:warm_coldwarm -0.06 0.06 -0.19
## trait_typesurvival:warm_coldwarm -0.01 0.04 -0.09
## trait_typebody_size:warm_coldcold:assay_temp_diff 0.00 0.00 -0.01
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.08 0.01 -0.09
## trait_typesurvival:warm_coldcold:assay_temp_diff -0.01 0.01 -0.04
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.00 0.00 -0.00
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.00 0.00 -0.01
## trait_typesurvival:warm_coldwarm:assay_temp_diff 0.00 0.00 -0.00
## u-95% CI Rhat Bulk_ESS
## trait_typebody_size:warm_coldcold 0.09 1.00 2426
## trait_typefecundity:warm_coldcold -0.21 1.00 3277
## trait_typesurvival:warm_coldcold 0.20 1.00 4888
## trait_typebody_size:warm_coldwarm 0.04 1.00 2763
## trait_typefecundity:warm_coldwarm 0.07 1.00 2401
## trait_typesurvival:warm_coldwarm 0.07 1.00 3135
## trait_typebody_size:warm_coldcold:assay_temp_diff 0.01 1.00 3391
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.07 1.00 6613
## trait_typesurvival:warm_coldcold:assay_temp_diff 0.02 1.00 9558
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.00 1.00 4399
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.01 1.00 4738
## trait_typesurvival:warm_coldwarm:assay_temp_diff 0.01 1.00 4975
## Tail_ESS
## trait_typebody_size:warm_coldcold 3022
## trait_typefecundity:warm_coldcold 4975
## trait_typesurvival:warm_coldcold 5736
## trait_typebody_size:warm_coldwarm 3218
## trait_typefecundity:warm_coldwarm 3745
## trait_typesurvival:warm_coldwarm 3671
## trait_typebody_size:warm_coldcold:assay_temp_diff 4414
## trait_typefecundity:warm_coldcold:assay_temp_diff 6434
## trait_typesurvival:warm_coldcold:assay_temp_diff 6100
## trait_typebody_size:warm_coldwarm:assay_temp_diff 5621
## trait_typefecundity:warm_coldwarm:assay_temp_diff 5238
## trait_typesurvival:warm_coldwarm:assay_temp_diff 5722
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnRR_model_concerns, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(Parameter = gsub("trait_type([a-z_]+):warm_cold(cold|warm)", "\\1(\\2)",
Parameter), Parameter = gsub("trait_type([a-z_]+)\\(warm\\):assay_temp_diff",
"\\1(warm): assay_temp_diff", Parameter)) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size(cold) | 0.038 | -0.012 | 0.086 |
| fecundity(cold) | -0.406 | -0.619 | -0.206 |
| survival(cold) | 0.063 | -0.076 | 0.203 |
| body_size(warm) | -0.008 | -0.055 | 0.038 |
| fecundity(warm) | -0.062 | -0.188 | 0.066 |
| survival(warm) | -0.009 | -0.089 | 0.074 |
| body_size(cold):assay_temp_diff | 0.000 | -0.006 | 0.007 |
| fecundity(cold):assay_temp_diff | -0.077 | -0.087 | -0.067 |
| survival(cold):assay_temp_diff | -0.010 | -0.038 | 0.018 |
| body_size(warm):assay_temp_diff | 0.000 | -0.004 | 0.004 |
| fecundity(warm):assay_temp_diff | 0.001 | -0.007 | 0.010 |
| survival(warm):assay_temp_diff | 0.002 | -0.004 | 0.007 |
Data visualisation
# Discard observations flagged because the number of generations of selection was unclear, as this would only affect the results of meta-regressions
data <- mutate(data, minor_concerns2 = ifelse(minor_concerns == "gen_selection approximate" |
minor_concerns == "gen_selection underestimated",
NA, minor_concerns))
# Filter to data without sources of procedural concerns.
data_concerns <- filter(data, is.na(major_concerns)==T & is.na(minor_concerns2)==T)
# Calculate sample sizes and study counts
sample_sizes_concerns <- data_concerns %>%
group_by(trait_type, warm_cold) %>%
summarise(
estimates = n(),
studies = n_distinct(ref)
)
# Generate predictions
emmeans_concerns <- as.data.frame(emmeans(
lnRR_model_concerns,
specs = ~ assay_temp_diff | trait_type * warm_cold,
at = list(assay_temp_diff = seq(min(data_concerns$assay_temp_diff), max(data_concerns$assay_temp_diff), by = 0.5)))
)
# Calculate range of values for each category
range_df_concerns <- data_concerns %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_assay_temp_diff = min(assay_temp_diff),
max_assay_temp_diff = max(assay_temp_diff)
)
emmeans_concerns$trait_type <- factor(emmeans_concerns$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_concerns$warm_cold <- factor(emmeans_concerns$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_concerns <- emmeans_concerns %>%
left_join(range_df_concerns, by = c("trait_type", "warm_cold")) %>%
filter(
assay_temp_diff >= min_assay_temp_diff,
assay_temp_diff <= max_assay_temp_diff
)
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_vline(xintercept = 0, # Vertical line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data_concerns, # Effect sizes, scaled by precision
aes(x = assay_temp_diff,
y = lnRR,
size = 1/sqrt(var_lnRR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_concerns, # Shaded area for credible intervals
aes(x = assay_temp_diff,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_concerns, # Predicted regression line
aes(y = emmean,
x = assay_temp_diff,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes_concerns,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -0.9, -1.3), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Assay temperature difference", y = "lnRR",
col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 25, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 22)) +
guides(fill = "none", size = "none")+
coord_cartesian(ylim = c(-1.5, 1.5))
ggsave(file = "fig/lnRR_concerns.png", width = 12, height = 7, dpi = 500)
Changes in relative trait variance (lnCVR)
Constant vs. increasing temperatures
Model specification
# Model specification
formula <- bf(lnCVR ~ 0 + trait_type:constant_increasing + trait_type:constant_increasing:assay_temp_diff + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix_warm)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnCVR_warm)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnCVR_model_constant_increasing <- brm(formula,
family = gaussian(),
data = data_warm,
data2 = list(phylo_matrix_warm = phylo_matrix_warm,
VCV_lnCVR_warm = VCV_lnCVR_warm),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnCVR_model_constant_increasing, file = "RData/lnCVR_model_constant_increasing.rds")
Model output
# Load model
lnCVR_model_constant_increasing <- readRDS("RData/lnCVR_model_constant_increasing.rds")
# Display model output
summary(lnCVR_model_constant_increasing)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnCVR ~ 0 + trait_type:constant_increasing + trait_type:constant_increasing:assay_temp_diff + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix_warm)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnCVR_warm)
## Data: data_warm (Number of observations: 381)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 81)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.09 0.06 0.00 0.21 1.00 1159 2902
##
## ~obs (Number of levels: 381)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.44 0.05 0.34 0.54 1.00 2072 3235
##
## ~phylogeny (Number of levels: 23)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.11 0.09 0.00 0.32 1.00 2872 4272
##
## ~ref (Number of levels: 38)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.08 0.06 0.00
## sd(trait_typefecundity) 0.10 0.08 0.00
## sd(trait_typesurvival) 0.62 0.19 0.31
## cor(trait_typebody_size,trait_typefecundity) 0.00 0.50 -0.87
## cor(trait_typebody_size,trait_typesurvival) -0.03 0.49 -0.87
## cor(trait_typefecundity,trait_typesurvival) -0.03 0.47 -0.86
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.23 1.00 3107 3994
## sd(trait_typefecundity) 0.31 1.00 2207 3874
## sd(trait_typesurvival) 1.05 1.00 3297 3717
## cor(trait_typebody_size,trait_typefecundity) 0.88 1.00 4662 5294
## cor(trait_typebody_size,trait_typesurvival) 0.87 1.00 1110 2641
## cor(trait_typefecundity,trait_typesurvival) 0.84 1.00 1504 2867
##
## ~species (Number of levels: 23)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.08 0.06 0.00 0.23 1.00 3018 4221
##
## Regression Coefficients:
## Estimate
## trait_typebody_size:constant_increasingconstant 0.06
## trait_typefecundity:constant_increasingconstant -0.06
## trait_typesurvival:constant_increasingconstant 0.28
## trait_typebody_size:constant_increasingincreasing -0.08
## trait_typefecundity:constant_increasingincreasing 0.02
## trait_typesurvival:constant_increasingincreasing 0.18
## trait_typebody_size:constant_increasingconstant:assay_temp_diff -0.00
## trait_typefecundity:constant_increasingconstant:assay_temp_diff 0.02
## trait_typesurvival:constant_increasingconstant:assay_temp_diff -0.03
## trait_typebody_size:constant_increasingincreasing:assay_temp_diff 0.07
## trait_typefecundity:constant_increasingincreasing:assay_temp_diff 0.01
## trait_typesurvival:constant_increasingincreasing:assay_temp_diff -0.03
## Est.Error
## trait_typebody_size:constant_increasingconstant 0.13
## trait_typefecundity:constant_increasingconstant 0.12
## trait_typesurvival:constant_increasingconstant 0.24
## trait_typebody_size:constant_increasingincreasing 0.28
## trait_typefecundity:constant_increasingincreasing 0.19
## trait_typesurvival:constant_increasingincreasing 0.46
## trait_typebody_size:constant_increasingconstant:assay_temp_diff 0.02
## trait_typefecundity:constant_increasingconstant:assay_temp_diff 0.02
## trait_typesurvival:constant_increasingconstant:assay_temp_diff 0.03
## trait_typebody_size:constant_increasingincreasing:assay_temp_diff 0.06
## trait_typefecundity:constant_increasingincreasing:assay_temp_diff 0.04
## trait_typesurvival:constant_increasingincreasing:assay_temp_diff 0.06
## l-95% CI
## trait_typebody_size:constant_increasingconstant -0.18
## trait_typefecundity:constant_increasingconstant -0.31
## trait_typesurvival:constant_increasingconstant -0.18
## trait_typebody_size:constant_increasingincreasing -0.63
## trait_typefecundity:constant_increasingincreasing -0.34
## trait_typesurvival:constant_increasingincreasing -0.71
## trait_typebody_size:constant_increasingconstant:assay_temp_diff -0.04
## trait_typefecundity:constant_increasingconstant:assay_temp_diff -0.02
## trait_typesurvival:constant_increasingconstant:assay_temp_diff -0.08
## trait_typebody_size:constant_increasingincreasing:assay_temp_diff -0.05
## trait_typefecundity:constant_increasingincreasing:assay_temp_diff -0.07
## trait_typesurvival:constant_increasingincreasing:assay_temp_diff -0.16
## u-95% CI Rhat
## trait_typebody_size:constant_increasingconstant 0.33 1.00
## trait_typefecundity:constant_increasingconstant 0.18 1.00
## trait_typesurvival:constant_increasingconstant 0.76 1.00
## trait_typebody_size:constant_increasingincreasing 0.49 1.00
## trait_typefecundity:constant_increasingincreasing 0.41 1.00
## trait_typesurvival:constant_increasingincreasing 1.12 1.00
## trait_typebody_size:constant_increasingconstant:assay_temp_diff 0.03 1.00
## trait_typefecundity:constant_increasingconstant:assay_temp_diff 0.06 1.00
## trait_typesurvival:constant_increasingconstant:assay_temp_diff 0.03 1.00
## trait_typebody_size:constant_increasingincreasing:assay_temp_diff 0.18 1.00
## trait_typefecundity:constant_increasingincreasing:assay_temp_diff 0.08 1.00
## trait_typesurvival:constant_increasingincreasing:assay_temp_diff 0.09 1.00
## Bulk_ESS
## trait_typebody_size:constant_increasingconstant 2678
## trait_typefecundity:constant_increasingconstant 3157
## trait_typesurvival:constant_increasingconstant 4163
## trait_typebody_size:constant_increasingincreasing 4000
## trait_typefecundity:constant_increasingincreasing 3696
## trait_typesurvival:constant_increasingincreasing 4982
## trait_typebody_size:constant_increasingconstant:assay_temp_diff 4749
## trait_typefecundity:constant_increasingconstant:assay_temp_diff 4943
## trait_typesurvival:constant_increasingconstant:assay_temp_diff 5797
## trait_typebody_size:constant_increasingincreasing:assay_temp_diff 5135
## trait_typefecundity:constant_increasingincreasing:assay_temp_diff 5524
## trait_typesurvival:constant_increasingincreasing:assay_temp_diff 6663
## Tail_ESS
## trait_typebody_size:constant_increasingconstant 3348
## trait_typefecundity:constant_increasingconstant 3664
## trait_typesurvival:constant_increasingconstant 4445
## trait_typebody_size:constant_increasingincreasing 5079
## trait_typefecundity:constant_increasingincreasing 4163
## trait_typesurvival:constant_increasingincreasing 5028
## trait_typebody_size:constant_increasingconstant:assay_temp_diff 5921
## trait_typefecundity:constant_increasingconstant:assay_temp_diff 5730
## trait_typesurvival:constant_increasingconstant:assay_temp_diff 6127
## trait_typebody_size:constant_increasingincreasing:assay_temp_diff 5816
## trait_typefecundity:constant_increasingincreasing:assay_temp_diff 5587
## trait_typesurvival:constant_increasingincreasing:assay_temp_diff 6404
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnCVR_model_constant_increasing, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(
# Simplify `trait_type` and `constant_increasing` interactions
Parameter = gsub("trait_type([a-z_]+):constant_increasing(constant|increasing)", "\\1(\\2)", Parameter),
# Adjust for assay_temp_diff
Parameter = gsub("trait_type([a-z_]+)\\((constant|increasing)\\):assay_temp_diff", "\\1(\\2): assay_temp_diff", Parameter)
) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~ round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size(constant) | 0.061 | -0.175 | 0.330 |
| fecundity(constant) | -0.063 | -0.314 | 0.184 |
| survival(constant) | 0.280 | -0.178 | 0.757 |
| body_size(increasing) | -0.076 | -0.632 | 0.490 |
| fecundity(increasing) | 0.019 | -0.342 | 0.415 |
| survival(increasing) | 0.185 | -0.712 | 1.122 |
| body_size(constant):assay_temp_diff | -0.004 | -0.041 | 0.033 |
| fecundity(constant):assay_temp_diff | 0.020 | -0.016 | 0.056 |
| survival(constant):assay_temp_diff | -0.028 | -0.082 | 0.027 |
| body_size(increasing):assay_temp_diff | 0.066 | -0.052 | 0.181 |
| fecundity(increasing):assay_temp_diff | 0.007 | -0.068 | 0.083 |
| survival(increasing):assay_temp_diff | -0.031 | -0.157 | 0.094 |
Contrasts
# Generate predictions
emms <- emmeans(lnCVR_model_constant_increasing, specs = ~constant_increasing | trait_type,
at = list(assay_temp_diff = 0))
# Generate contrasts
contrast(emms, method = "pairwise")
## trait_type = body_size:
## contrast estimate lower.HPD upper.HPD
## constant - increasing 0.1337 -0.400 0.717
##
## trait_type = fecundity:
## contrast estimate lower.HPD upper.HPD
## constant - increasing -0.0765 -0.483 0.326
##
## trait_type = survival:
## contrast estimate lower.HPD upper.HPD
## constant - increasing 0.1066 -0.870 1.087
##
## Point estimate displayed: median
## HPD interval probability: 0.95
Data visualisation
# Generate predictions
emmeans_constant_increasing <- as.data.frame(emmeans(lnCVR_model_constant_increasing,
specs = ~ constant_increasing | trait_type,
at = list(assay_temp_diff = 0)))
emmeans_constant_increasing$trait_type <- factor(emmeans_constant_increasing$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_constant_increasing$constant_increasing <- factor(emmeans_constant_increasing$constant_increasing,
levels = c("constant", "increasing"),
labels = c("Constant", "Increasing"))
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75,
lwd=1) +
geom_quasirandom(data = data_warm, # Plot effect sizes, scaled by precision
aes(x = trait_type:constant_increasing,
y = lnCVR,
fill = trait_type:constant_increasing,
size = 1/sqrt(var_lnCVR)),
color = "black",
shape = 21,
width = 0.25,
alpha = 0.45) +
geom_errorbar(data = emmeans_constant_increasing, # Plot the point estimates in white for background
aes(x = trait_type:constant_increasing,
ymin = lower.HPD,
ymax = upper.HPD),
color="white",
size = 2.5,
width = 0.17) +
geom_point(data = emmeans_constant_increasing, # Plot the credible intervals in white for background
aes(x = trait_type:constant_increasing,
y = emmean),
shape = 21,
size = 4,
stroke = 2,
color="white",
fill = "white") +
geom_errorbar(data = emmeans_constant_increasing, # Plot the point estimates
aes(x = trait_type:constant_increasing,
ymin = lower.HPD,
ymax = upper.HPD),
color="black",
size = 2,
width = 0.15) +
geom_point(data = emmeans_constant_increasing, # Plot the credible intervals
aes(x = trait_type:constant_increasing,
y = emmean),
shape = 21,
size = 3.5,
stroke = 2,
color="black",
fill = "white") +
geom_text(data = sample_sizes_warm, # Sample size annotations
aes(x = trait_type:constant_increasing,
y = 3,
label = paste0("k = ", estimates, " (", studies, ")"),
color = constant_increasing),
hjust = 1, size = 5, show.legend = FALSE) +
theme_bw() + # Customize the plot
labs(y = "lnCVR", x = "") +
scale_size_continuous(range = c(2, 8))+
scale_fill_manual(values = c("#8E77FF", "#FF6040", "#8E77FF", "#FF6040", "#8E77FF", "#FF6040"))+
scale_color_manual(values = c("Constant" = "#8E77FF", "Increasing" = "#FF6040")) + # Text colour
theme(text = element_text(size = 20, color = "black"),
legend.title = ggplot2::element_text(size = 16),
legend.text = ggplot2::element_text(size = 14),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5)) +
guides(fill = "none", size = "none")+
coord_flip() +
ylim(-3, 3)
ggsave(file = "fig/lnCVR_constant_increasing.png", width = 12, height = 7, dpi = 500)
Leave-one-out analysis
Model specification
# Get a list of study IDs for the LOO
study_ids <- unique(data$ref)
num_studies <- length(study_ids)
# Initialize a list to store the results
loo_results <- vector("list", num_studies)
names(loo_results) <- study_ids
# Set up parallel processing (16 models at a time)
plan(multisession, workers = 16)
# Model specification
formula <- bf(lnCVR ~ trait_type -1 + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnCVR)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Function to fit the model after iteratively removing one study
leave_one_out <- function(study_id) {
# Subset the data
data_subset <- subset(data, ref != study_id)
# Recalculate VCV matrix
VCV_lnCVR_subset <- vcalc(
vi = var_lnCVR,
cluster = shared_control_ID,
rho = 0.5,
obs = obs,
data = data_subset
)
# Fit the model
model_subset <- brm(
formula,
family = gaussian(),
data = data_subset,
data2 = list(
phylo_matrix = phylo_matrix,
VCV_lnCVR = VCV_lnCVR_subset
),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 2000, # Reduce to 2000 iterations to reduce computational demands
warmup = 1000,
chains = 2, # Only two chains to reduce computational demands
cores = 1, # Set cores to 1 inside the function
seed = 123
)
# Extract the fixed effects
fixed_effects <- fixef(model_subset)
# Return the fixed effects
return(list(study_id = study_id, fixed_effects = fixed_effects))
}
# Run the function in parallel over the studies
loo_results <- future_lapply(study_ids, leave_one_out)
# Save results
saveRDS(loo_results, file = "RData/lnCVR_LOO_results.rds")
Model summary
# Load the list of all model outputs
loo_lnCVR <- readRDS(file = "RData/lnCVR_LOO_results.rds")
# Extract data on fixed effects from each model
loo_results_lnCVR <- do.call(rbind, lapply(loo_lnCVR, function(x) {
# Extract study_id and fixed_effects data
study_id <- x$study_id
fixed_effects <- x$fixed_effects
# Convert row names (trait_type) to a column, remove prefix, and add
# study_id
fixed_effects <- data.frame(trait_type = sub("trait_type", "", rownames(fixed_effects)),
fixed_effects)
fixed_effects$study_id <- study_id
# Remove row names
rownames(fixed_effects) <- NULL
return(fixed_effects)
}))
loo_results_lnCVR$trait_type <- factor(loo_results_lnCVR$trait_type, levels = c("body_size",
"fecundity", "survival"), labels = c("Body size", "Fecundity", "Survival"))
# Summarise the data
loo_results_lnCVR %>%
group_by(trait_type) %>%
summarise(Estimate = mean(Estimate), SE = mean(Est.Error), lower_CI = mean(Q2.5),
upper_CI = mean(Q97.5)) # Mean across all models
## # A tibble: 3 × 5
## trait_type Estimate SE lower_CI upper_CI
## <fct> <dbl> <dbl> <dbl> <dbl>
## 1 Body size 0.0443 0.0985 -0.139 0.254
## 2 Fecundity -0.0115 0.101 -0.204 0.197
## 3 Survival 0.129 0.159 -0.181 0.448
Data visualisation
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75,
lwd=1) +
geom_errorbar(data = loo_results_lnCVR, # Plot the credible intervals in white for background
aes(x = study_id,
y = Estimate,
ymin = Q2.5,
ymax = Q97.5,
color = trait_type),
shape = 21,
size = 1,
width = 0.3) +
geom_point(data = loo_results_lnCVR,
aes(x = study_id,
y = Estimate,
fill = trait_type),
shape = 21,
size = 4,
stroke = 1,
color="black") +
theme_bw() + # Customize the plot
labs(y = "lnCVR", x = "") +
scale_fill_manual(values = c("#73B706", "#06A2BA", "#B90674"))+
scale_color_manual(values = c("#73B706", "#06A2BA", "#B90674")) +
theme(text = element_text(size = 20, color = "black"),
legend.title = ggplot2::element_text(size = 16),
legend.text = ggplot2::element_text(size = 14),
axis.text.y = ggplot2::element_text(size = 10,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 20)) +
guides(fill = "none", color = "none")+
coord_flip()+
xlim(-0.4, 0.4)+
facet_wrap(~trait_type, ncol = 3)+
scale_x_discrete(limits = rev(levels(as.factor(loo_results_lnCVR$study_id)))) # Alphabetical order
ggsave(file = "fig/lnCVR_LOO.png", width = 12, height = 12, dpi = 500)
Unusual study designs
Model specification
# Discard observations flagged because the number of generations of selection was unclear, as this would only affect the results of meta-regressions
data <- mutate(data, minor_concerns2 = ifelse(minor_concerns == "gen_selection approximate" |
minor_concerns == "gen_selection underestimated",
NA, minor_concerns))
# Filter to data without sources of procedural concerns.
data_concerns <- filter(data, is.na(major_concerns)==T & is.na(minor_concerns2)==T)
# Drop tips that are not in the warm dataset
tips_to_drop <- setdiff(phylo_tree$tip.label, data_concerns$phylogeny)
phylo_tree_concerns <- drop.tip(phylo_tree, tips_to_drop)
# Compute phylogenetic correlation matrix
phylo_matrix_concerns <- vcv(phylo_tree_concerns, cor = T) # The vcv function returns a variance-covariance matrix
# Convert tibble to data frame
data_concerns <- as.data.frame(data_concerns)
# Calculate VCV matrix
VCV_lnCVR_concerns <- vcalc(vi = var_lnCVR,
cluster = shared_control_ID,
rho = 0.5,
obs = obs,
data = data_concerns)
# Model specification
formula <- bf(lnCVR ~ 0 + trait_type:warm_cold + trait_type:warm_cold:assay_temp_diff + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix_concerns)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnCVR_concerns)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnCVR_model_concerns <- brm(formula,
family = gaussian(),
data = data_concerns,
data2 = list(phylo_matrix_concerns = phylo_matrix_concerns,
VCV_lnCVR_concerns = VCV_lnCVR_concerns),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnCVR_model_concerns, file = "RData/lnCVR_model_concerns.rds")
Model summary
# Load model
lnCVR_model_concerns <- readRDS("RData/lnCVR_model_concerns.rds")
# Display model output
summary(lnCVR_model_concerns)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnCVR ~ 0 + trait_type:warm_cold + trait_type:warm_cold:assay_temp_diff + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix_concerns)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnCVR_concerns)
## Data: data_concerns (Number of observations: 321)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 72)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.13 0.07 0.01 0.26 1.00 1347 2050
##
## ~obs (Number of levels: 321)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.41 0.06 0.29 0.53 1.00 1660 3010
##
## ~phylogeny (Number of levels: 22)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.10 0.09 0.00 0.32 1.00 3339 4425
##
## ~ref (Number of levels: 33)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.08 0.07 0.00
## sd(trait_typefecundity) 0.11 0.09 0.00
## sd(trait_typesurvival) 0.68 0.21 0.33
## cor(trait_typebody_size,trait_typefecundity) 0.04 0.49 -0.86
## cor(trait_typebody_size,trait_typesurvival) -0.06 0.50 -0.90
## cor(trait_typefecundity,trait_typesurvival) -0.07 0.47 -0.87
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.24 1.00 3827 4841
## sd(trait_typefecundity) 0.33 1.00 2626 3833
## sd(trait_typesurvival) 1.17 1.00 3113 4527
## cor(trait_typebody_size,trait_typefecundity) 0.88 1.00 5647 5687
## cor(trait_typebody_size,trait_typesurvival) 0.87 1.00 1193 2127
## cor(trait_typefecundity,trait_typesurvival) 0.83 1.00 1742 3608
##
## ~species (Number of levels: 22)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.08 0.06 0.00 0.25 1.00 3552 4619
##
## Regression Coefficients:
## Estimate Est.Error l-95% CI
## trait_typebody_size:warm_coldcold 0.07 0.17 -0.25
## trait_typefecundity:warm_coldcold -0.21 0.31 -0.83
## trait_typesurvival:warm_coldcold 0.15 0.55 -0.90
## trait_typebody_size:warm_coldwarm 0.00 0.13 -0.25
## trait_typefecundity:warm_coldwarm -0.03 0.12 -0.27
## trait_typesurvival:warm_coldwarm 0.26 0.24 -0.20
## trait_typebody_size:warm_coldcold:assay_temp_diff -0.04 0.06 -0.15
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.14 0.06 -0.26
## trait_typesurvival:warm_coldcold:assay_temp_diff -0.00 0.12 -0.24
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.02 0.02 -0.02
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.02 0.02 -0.02
## trait_typesurvival:warm_coldwarm:assay_temp_diff -0.03 0.03 -0.08
## u-95% CI Rhat Bulk_ESS
## trait_typebody_size:warm_coldcold 0.39 1.00 3506
## trait_typefecundity:warm_coldcold 0.38 1.00 4899
## trait_typesurvival:warm_coldcold 1.24 1.00 6366
## trait_typebody_size:warm_coldwarm 0.26 1.00 3482
## trait_typefecundity:warm_coldwarm 0.22 1.00 3891
## trait_typesurvival:warm_coldwarm 0.75 1.00 4052
## trait_typebody_size:warm_coldcold:assay_temp_diff 0.07 1.00 7840
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.02 1.00 6417
## trait_typesurvival:warm_coldcold:assay_temp_diff 0.24 1.00 9296
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.07 1.00 4828
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.06 1.00 6885
## trait_typesurvival:warm_coldwarm:assay_temp_diff 0.03 1.00 6289
## Tail_ESS
## trait_typebody_size:warm_coldcold 4975
## trait_typefecundity:warm_coldcold 5488
## trait_typesurvival:warm_coldcold 5853
## trait_typebody_size:warm_coldwarm 4019
## trait_typefecundity:warm_coldwarm 3551
## trait_typesurvival:warm_coldwarm 4079
## trait_typebody_size:warm_coldcold:assay_temp_diff 6553
## trait_typefecundity:warm_coldcold:assay_temp_diff 6530
## trait_typesurvival:warm_coldcold:assay_temp_diff 6783
## trait_typebody_size:warm_coldwarm:assay_temp_diff 6069
## trait_typefecundity:warm_coldwarm:assay_temp_diff 6018
## trait_typesurvival:warm_coldwarm:assay_temp_diff 6417
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnCVR_model_concerns, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(Parameter = gsub("trait_type([a-z_]+):warm_cold(cold|warm)", "\\1(\\2)",
Parameter), Parameter = gsub("trait_type([a-z_]+)\\(warm\\):assay_temp_diff",
"\\1(warm): assay_temp_diff", Parameter)) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size(cold) | 0.068 | -0.251 | 0.393 |
| fecundity(cold) | -0.214 | -0.831 | 0.384 |
| survival(cold) | 0.155 | -0.897 | 1.244 |
| body_size(warm) | 0.002 | -0.250 | 0.263 |
| fecundity(warm) | -0.031 | -0.266 | 0.220 |
| survival(warm) | 0.260 | -0.198 | 0.749 |
| body_size(cold):assay_temp_diff | -0.042 | -0.152 | 0.071 |
| fecundity(cold):assay_temp_diff | -0.135 | -0.256 | -0.016 |
| survival(cold):assay_temp_diff | -0.004 | -0.243 | 0.236 |
| body_size(warm):assay_temp_diff | 0.020 | -0.024 | 0.065 |
| fecundity(warm):assay_temp_diff | 0.020 | -0.015 | 0.056 |
| survival(warm):assay_temp_diff | -0.029 | -0.083 | 0.026 |
Data visualisation
# Discard observations flagged because the number of generations of selection was unclear, as this would only affect the results of meta-regressions
data <- mutate(data, minor_concerns2 = ifelse(minor_concerns == "gen_selection approximate" |
minor_concerns == "gen_selection underestimated",
NA, minor_concerns))
# Filter to data without sources of procedural concerns.
data_concerns <- filter(data, is.na(major_concerns)==T & is.na(minor_concerns2)==T)
# Calculate sample sizes and study counts
sample_sizes_concerns <- data_concerns %>%
group_by(trait_type, warm_cold) %>%
summarise(
estimates = n(),
studies = n_distinct(ref)
)
# Generate predictions
emmeans_concerns <- as.data.frame(emmeans(
lnCVR_model_concerns,
specs = ~ assay_temp_diff | trait_type * warm_cold,
at = list(assay_temp_diff = seq(min(data_concerns$assay_temp_diff), max(data_concerns$assay_temp_diff), by = 0.5))))
# Calculate range of values for each category
range_df_concerns <- data_concerns %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_assay_temp_diff = min(assay_temp_diff),
max_assay_temp_diff = max(assay_temp_diff)
)
emmeans_concerns$trait_type <- factor(emmeans_concerns$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_concerns$warm_cold <- factor(emmeans_concerns$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_concerns <- emmeans_concerns %>%
left_join(range_df_concerns, by = c("trait_type", "warm_cold")) %>%
filter(
assay_temp_diff >= min_assay_temp_diff,
assay_temp_diff <= max_assay_temp_diff
)
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_vline(xintercept = 0, # Vertical line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data_concerns, # Effect sizes, scaled by precision
aes(x = assay_temp_diff,
y = lnCVR,
size = 1/sqrt(var_lnCVR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_concerns, # Shaded area for credible intervals
aes(x = assay_temp_diff,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_concerns, # Predicted regression line
aes(y = emmean,
x = assay_temp_diff,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes_concerns,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -2, -2.75), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Assay temperature difference", y = "lnCVR",
col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 25, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 22)) +
guides(fill = "none", size = "none")+
coord_cartesian(ylim = c(-3, 3))
ggsave(file = "fig/lnCVR_concerns.png", width = 12, height = 7, dpi = 500)
Changes in trait variance (lnVR)
Constant vs. increasing temperatures
Model specification
# Model specification
formula <- bf(lnVR ~ 0 + trait_type:constant_increasing + trait_type:constant_increasing:assay_temp_diff + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix_warm)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnVR_warm)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnVR_model_constant_increasing <- brm(formula,
family = gaussian(),
data = data_warm,
data2 = list(phylo_matrix_warm = phylo_matrix_warm,
VCV_lnVR_warm = VCV_lnVR_warm),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnVR_model_constant_increasing, file = "RData/lnVR_model_constant_increasing.rds")
Model output
# Load model
lnVR_model_constant_increasing <- readRDS("RData/lnVR_model_constant_increasing.rds")
# Display model output
summary(lnVR_model_constant_increasing)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnVR ~ 0 + trait_type:constant_increasing + trait_type:constant_increasing:assay_temp_diff + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix_warm)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnVR_warm)
## Data: data_warm (Number of observations: 381)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 81)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.10 0.06 0.00 0.24 1.01 590 1729
##
## ~obs (Number of levels: 381)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.76 0.04 0.68 0.84 1.00 2368 4730
##
## ~phylogeny (Number of levels: 23)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.12 0.09 0.01 0.35 1.00 1683 3203
##
## ~ref (Number of levels: 38)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.09 0.08 0.00
## sd(trait_typefecundity) 0.10 0.08 0.00
## sd(trait_typesurvival) 0.31 0.18 0.02
## cor(trait_typebody_size,trait_typefecundity) 0.02 0.50 -0.88
## cor(trait_typebody_size,trait_typesurvival) -0.02 0.49 -0.89
## cor(trait_typefecundity,trait_typesurvival) -0.02 0.49 -0.88
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.28 1.00 1982 3248
## sd(trait_typefecundity) 0.29 1.00 1449 2442
## sd(trait_typesurvival) 0.68 1.00 994 1772
## cor(trait_typebody_size,trait_typefecundity) 0.89 1.00 3136 4437
## cor(trait_typebody_size,trait_typesurvival) 0.85 1.00 1634 3846
## cor(trait_typefecundity,trait_typesurvival) 0.86 1.00 1835 3569
##
## ~species (Number of levels: 23)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.08 0.06 0.00 0.23 1.00 1989 3398
##
## Regression Coefficients:
## Estimate
## trait_typebody_size:constant_increasingconstant 0.05
## trait_typefecundity:constant_increasingconstant -0.15
## trait_typesurvival:constant_increasingconstant 0.25
## trait_typebody_size:constant_increasingincreasing -0.11
## trait_typefecundity:constant_increasingincreasing 0.01
## trait_typesurvival:constant_increasingincreasing 0.06
## trait_typebody_size:constant_increasingconstant:assay_temp_diff -0.01
## trait_typefecundity:constant_increasingconstant:assay_temp_diff 0.03
## trait_typesurvival:constant_increasingconstant:assay_temp_diff -0.02
## trait_typebody_size:constant_increasingincreasing:assay_temp_diff 0.08
## trait_typefecundity:constant_increasingincreasing:assay_temp_diff -0.01
## trait_typesurvival:constant_increasingincreasing:assay_temp_diff -0.01
## Est.Error
## trait_typebody_size:constant_increasingconstant 0.15
## trait_typefecundity:constant_increasingconstant 0.14
## trait_typesurvival:constant_increasingconstant 0.19
## trait_typebody_size:constant_increasingincreasing 0.34
## trait_typefecundity:constant_increasingincreasing 0.21
## trait_typesurvival:constant_increasingincreasing 0.38
## trait_typebody_size:constant_increasingconstant:assay_temp_diff 0.02
## trait_typefecundity:constant_increasingconstant:assay_temp_diff 0.02
## trait_typesurvival:constant_increasingconstant:assay_temp_diff 0.03
## trait_typebody_size:constant_increasingincreasing:assay_temp_diff 0.07
## trait_typefecundity:constant_increasingincreasing:assay_temp_diff 0.04
## trait_typesurvival:constant_increasingincreasing:assay_temp_diff 0.06
## l-95% CI
## trait_typebody_size:constant_increasingconstant -0.24
## trait_typefecundity:constant_increasingconstant -0.42
## trait_typesurvival:constant_increasingconstant -0.12
## trait_typebody_size:constant_increasingincreasing -0.78
## trait_typefecundity:constant_increasingincreasing -0.39
## trait_typesurvival:constant_increasingincreasing -0.65
## trait_typebody_size:constant_increasingconstant:assay_temp_diff -0.05
## trait_typefecundity:constant_increasingconstant:assay_temp_diff -0.01
## trait_typesurvival:constant_increasingconstant:assay_temp_diff -0.08
## trait_typebody_size:constant_increasingincreasing:assay_temp_diff -0.06
## trait_typefecundity:constant_increasingincreasing:assay_temp_diff -0.10
## trait_typesurvival:constant_increasingincreasing:assay_temp_diff -0.13
## u-95% CI Rhat
## trait_typebody_size:constant_increasingconstant 0.34 1.00
## trait_typefecundity:constant_increasingconstant 0.12 1.00
## trait_typesurvival:constant_increasingconstant 0.65 1.00
## trait_typebody_size:constant_increasingincreasing 0.55 1.00
## trait_typefecundity:constant_increasingincreasing 0.44 1.00
## trait_typesurvival:constant_increasingincreasing 0.85 1.00
## trait_typebody_size:constant_increasingconstant:assay_temp_diff 0.03 1.00
## trait_typefecundity:constant_increasingconstant:assay_temp_diff 0.06 1.00
## trait_typesurvival:constant_increasingconstant:assay_temp_diff 0.03 1.00
## trait_typebody_size:constant_increasingincreasing:assay_temp_diff 0.21 1.00
## trait_typefecundity:constant_increasingincreasing:assay_temp_diff 0.08 1.00
## trait_typesurvival:constant_increasingincreasing:assay_temp_diff 0.11 1.00
## Bulk_ESS
## trait_typebody_size:constant_increasingconstant 1777
## trait_typefecundity:constant_increasingconstant 1885
## trait_typesurvival:constant_increasingconstant 2607
## trait_typebody_size:constant_increasingincreasing 1761
## trait_typefecundity:constant_increasingincreasing 2068
## trait_typesurvival:constant_increasingincreasing 2201
## trait_typebody_size:constant_increasingconstant:assay_temp_diff 2536
## trait_typefecundity:constant_increasingconstant:assay_temp_diff 2178
## trait_typesurvival:constant_increasingconstant:assay_temp_diff 2867
## trait_typebody_size:constant_increasingincreasing:assay_temp_diff 2042
## trait_typefecundity:constant_increasingincreasing:assay_temp_diff 2463
## trait_typesurvival:constant_increasingincreasing:assay_temp_diff 2589
## Tail_ESS
## trait_typebody_size:constant_increasingconstant 2909
## trait_typefecundity:constant_increasingconstant 3615
## trait_typesurvival:constant_increasingconstant 3794
## trait_typebody_size:constant_increasingincreasing 3411
## trait_typefecundity:constant_increasingincreasing 4043
## trait_typesurvival:constant_increasingincreasing 3335
## trait_typebody_size:constant_increasingconstant:assay_temp_diff 3923
## trait_typefecundity:constant_increasingconstant:assay_temp_diff 3832
## trait_typesurvival:constant_increasingconstant:assay_temp_diff 4642
## trait_typebody_size:constant_increasingincreasing:assay_temp_diff 3380
## trait_typefecundity:constant_increasingincreasing:assay_temp_diff 3799
## trait_typesurvival:constant_increasingincreasing:assay_temp_diff 4030
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnVR_model_constant_increasing, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(
# Simplify `trait_type` and `constant_increasing` interactions
Parameter = gsub("trait_type([a-z_]+):constant_increasing(constant|increasing)", "\\1(\\2)", Parameter),
# Adjust for assay_temp_diff
Parameter = gsub("trait_type([a-z_]+)\\((constant|increasing)\\):assay_temp_diff", "\\1(\\2): assay_temp_diff", Parameter)
) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~ round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size(constant) | 0.045 | -0.237 | 0.343 |
| fecundity(constant) | -0.146 | -0.418 | 0.124 |
| survival(constant) | 0.253 | -0.117 | 0.646 |
| body_size(increasing) | -0.114 | -0.777 | 0.550 |
| fecundity(increasing) | 0.014 | -0.394 | 0.445 |
| survival(increasing) | 0.065 | -0.646 | 0.855 |
| body_size(constant):assay_temp_diff | -0.009 | -0.054 | 0.034 |
| fecundity(constant):assay_temp_diff | 0.025 | -0.010 | 0.061 |
| survival(constant):assay_temp_diff | -0.024 | -0.079 | 0.031 |
| body_size(increasing):assay_temp_diff | 0.076 | -0.063 | 0.213 |
| fecundity(increasing):assay_temp_diff | -0.009 | -0.096 | 0.078 |
| survival(increasing):assay_temp_diff | -0.009 | -0.133 | 0.109 |
Contrasts
# Generate predictions
emms <- emmeans(lnVR_model_constant_increasing, specs = ~constant_increasing | trait_type,
at = list(assay_temp_diff = 0))
# Generate contrasts
contrast(emms, method = "pairwise")
## trait_type = body_size:
## contrast estimate lower.HPD upper.HPD
## constant - increasing 0.156 -0.541 0.824
##
## trait_type = fecundity:
## contrast estimate lower.HPD upper.HPD
## constant - increasing -0.157 -0.620 0.277
##
## trait_type = survival:
## contrast estimate lower.HPD upper.HPD
## constant - increasing 0.205 -0.610 0.969
##
## Point estimate displayed: median
## HPD interval probability: 0.95
Data visualisation
# Generate predictions
emmeans_constant_increasing <- as.data.frame(emmeans(lnVR_model_constant_increasing,
specs = ~ constant_increasing | trait_type,
at = list(assay_temp_diff = 0)))
emmeans_constant_increasing$trait_type <- factor(emmeans_constant_increasing$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_constant_increasing$constant_increasing <- factor(emmeans_constant_increasing$constant_increasing,
levels = c("constant", "increasing"),
labels = c("Constant", "Increasing"))
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75,
lwd=1) +
geom_quasirandom(data = data_warm, # Plot effect sizes, scaled by precision
aes(x = trait_type:constant_increasing,
y = lnVR,
fill = trait_type:constant_increasing,
size = 1/sqrt(var_lnVR)),
color = "black",
shape = 21,
width = 0.25,
alpha = 0.45) +
geom_errorbar(data = emmeans_constant_increasing, # Plot the point estimates in white for background
aes(x = trait_type:constant_increasing,
ymin = lower.HPD,
ymax = upper.HPD),
color="white",
size = 2.5,
width = 0.17) +
geom_point(data = emmeans_constant_increasing, # Plot the credible intervals in white for background
aes(x = trait_type:constant_increasing,
y = emmean),
shape = 21,
size = 4,
stroke = 2,
color="white",
fill = "white") +
geom_errorbar(data = emmeans_constant_increasing, # Plot the point estimates
aes(x = trait_type:constant_increasing,
ymin = lower.HPD,
ymax = upper.HPD),
color="black",
size = 2,
width = 0.15) +
geom_point(data = emmeans_constant_increasing, # Plot the credible intervals
aes(x = trait_type:constant_increasing,
y = emmean),
shape = 21,
size = 3.5,
stroke = 2,
color="black",
fill = "white") +
geom_text(data = sample_sizes_warm, # Sample size annotations
aes(x = trait_type:constant_increasing,
y = 3.5,
label = paste0("k = ", estimates, " (", studies, ")"),
color = constant_increasing),
hjust = 1, size = 5, show.legend = FALSE) +
theme_bw() + # Customize the plot
labs(y = "lnVR", x = "") +
scale_size_continuous(range = c(2, 8))+
scale_fill_manual(values = c("#8E77FF", "#FF6040", "#8E77FF", "#FF6040", "#8E77FF", "#FF6040"))+
scale_color_manual(values = c("Constant" = "#8E77FF", "Increasing" = "#FF6040")) + # Text colour
theme(text = element_text(size = 20, color = "black"),
legend.title = ggplot2::element_text(size = 16),
legend.text = ggplot2::element_text(size = 14),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5)) +
guides(fill = "none", size = "none")+
coord_flip() +
ylim(-3.5, 3.5)
ggsave(file = "fig/lnVR_constant_increasing.png", width = 12, height = 7, dpi = 500)
Leave-one-out analysis
Model specification
# Get a list of study IDs for the LOO
study_ids <- unique(data$ref)
num_studies <- length(study_ids)
# Initialize a list to store the results
loo_results <- vector("list", num_studies)
names(loo_results) <- study_ids
# Set up parallel processing (16 models at a time)
plan(multisession, workers = 16)
# Model specification
formula <- bf(lnVR ~ trait_type -1 + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnVR)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Function to fit the model after iteratively removing one study
leave_one_out <- function(study_id) {
# Subset the data
data_subset <- subset(data, ref != study_id)
# Recalculate VCV matrix
VCV_lnVR_subset <- vcalc(
vi = var_lnVR,
cluster = shared_control_ID,
rho = 0.5,
obs = obs,
data = data_subset
)
# Fit the model
model_subset <- brm(
formula,
family = gaussian(),
data = data_subset,
data2 = list(
phylo_matrix = phylo_matrix,
VCV_lnVR = VCV_lnVR_subset
),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 2000, # Reduce to 2000 iterations to reduce computational demands
warmup = 1000,
chains = 2, # Only two chains to reduce computational demands
cores = 1, # Set cores to 1 inside the function
seed = 123
)
# Extract the fixed effects
fixed_effects <- fixef(model_subset)
# Return the fixed effects
return(list(study_id = study_id, fixed_effects = fixed_effects))
}
# Run the function in parallel over the studies
loo_results <- future_lapply(study_ids, leave_one_out)
# Save results
saveRDS(loo_results, file = "RData/lnVR_LOO_results.rds")
Model summary
# Load the list of all model outputs
loo_lnVR <- readRDS(file = "RData/lnVR_LOO_results.rds")
# Extract data on fixed effects from each model
loo_results_lnVR <- do.call(rbind, lapply(loo_lnVR, function(x) {
# Extract study_id and fixed_effects data
study_id <- x$study_id
fixed_effects <- x$fixed_effects
# Convert row names (trait_type) to a column, remove prefix, and add
# study_id
fixed_effects <- data.frame(trait_type = sub("trait_type", "", rownames(fixed_effects)),
fixed_effects)
fixed_effects$study_id <- study_id
# Remove row names
rownames(fixed_effects) <- NULL
return(fixed_effects)
}))
loo_results_lnVR$trait_type <- factor(loo_results_lnVR$trait_type, levels = c("body_size",
"fecundity", "survival"), labels = c("Body size", "Fecundity", "Survival"))
# Summarise the data
loo_results_lnVR %>%
group_by(trait_type) %>%
summarise(Estimate = mean(Estimate), SE = mean(Est.Error), lower_CI = mean(Q2.5),
upper_CI = mean(Q97.5)) # Mean across all models
## # A tibble: 3 × 5
## trait_type Estimate SE lower_CI upper_CI
## <fct> <dbl> <dbl> <dbl> <dbl>
## 1 Body size 0.0272 0.104 -0.172 0.242
## 2 Fecundity -0.0576 0.0994 -0.254 0.143
## 3 Survival 0.105 0.129 -0.144 0.364
Data visualisation
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75,
lwd=1) +
geom_errorbar(data = loo_results_lnVR, # Plot the credible intervals in white for background
aes(x = study_id,
y = Estimate,
ymin = Q2.5,
ymax = Q97.5,
color = trait_type),
shape = 21,
size = 1,
width = 0.3) +
geom_point(data = loo_results_lnVR,
aes(x = study_id,
y = Estimate,
fill = trait_type),
shape = 21,
size = 4,
stroke = 1,
color="black") +
theme_bw() + # Customize the plot
labs(y = "lnVR", x = "") +
scale_fill_manual(values = c("#73B706", "#06A2BA", "#B90674"))+
scale_color_manual(values = c("#73B706", "#06A2BA", "#B90674")) +
theme(text = element_text(size = 20, color = "black"),
legend.title = ggplot2::element_text(size = 16),
legend.text = ggplot2::element_text(size = 14),
axis.text.y = ggplot2::element_text(size = 10,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 20)) +
guides(fill = "none", color = "none")+
coord_flip()+
xlim(-0.45, 0.45) +
facet_wrap(~trait_type, ncol = 3)+
scale_x_discrete(limits = rev(levels(as.factor(loo_results_lnVR$study_id)))) # Alphabetical order
ggsave(file = "fig/lnVR_LOO.png", width = 12, height = 12, dpi = 500)
Unusual study designs
Model specification
# Discard observations flagged because the number of generations of selection was unclear, as this would only affect the results of meta-regressions
data <- mutate(data, minor_concerns2 = ifelse(minor_concerns == "gen_selection approximate" |
minor_concerns == "gen_selection underestimated",
NA, minor_concerns))
# Filter to data without sources of procedural concerns.
data_concerns <- filter(data, is.na(major_concerns)==T & is.na(minor_concerns2)==T)
# Drop tips that are not in the warm dataset
tips_to_drop <- setdiff(phylo_tree$tip.label, data_concerns$phylogeny)
phylo_tree_concerns <- drop.tip(phylo_tree, tips_to_drop)
# Compute phylogenetic correlation matrix
phylo_matrix_concerns <- vcv(phylo_tree_concerns, cor = T) # The vcv function returns a variance-covariance matrix
# Convert tibble to data frame
data_concerns <- as.data.frame(data_concerns)
# Calculate VCV matrix
VCV_lnVR_concerns <- vcalc(vi = var_lnVR,
cluster = shared_control_ID,
rho = 0.5,
obs = obs,
data = data_concerns)
# Model specification
formula <- bf(lnVR ~ 0 + trait_type:warm_cold + trait_type:warm_cold:assay_temp_diff + # Separate effects by trait type
(trait_type-1|ref) + # Correlation between traits among studies
(1|species) + # Species-level random effect
(1|gr(phylogeny, cov = phylo_matrix_concerns)) + # Phylogenetic relatedness
(1|experiment_ID) + # Experiment-level random effect
(1|obs) + # Observation-level random effect
fcor(VCV_lnVR_concerns)) # Variance covariance matix of correlated sampling variances
# Define priors
prior = c(
prior(constant(1), class = "sigma")
) # Because the residual variance-covariance structure is specified in fcor, there is no need to estimate sigma so it is left as a constant
# Fit model
lnVR_model_concerns <- brm(formula,
family = gaussian(),
data = data_concerns,
data2 = list(phylo_matrix_concerns = phylo_matrix_concerns,
VCV_lnVR_concerns = VCV_lnVR_concerns),
prior = prior,
control = list(adapt_delta = 0.99, max_treedepth = 15),
iter = 4000,
warmup = 2000,
chains = 4,
cores = 4,
seed = 123)
# Save model
saveRDS(lnVR_model_concerns, file = "RData/lnVR_model_concerns.rds")
Model summary
# Load model
lnVR_model_concerns <- readRDS("RData/lnVR_model_concerns.rds")
# Display model output
summary(lnVR_model_concerns)
## Family: gaussian
## Links: mu = identity; sigma = identity
## Formula: lnVR ~ 0 + trait_type:warm_cold + trait_type:warm_cold:assay_temp_diff + (trait_type - 1 | ref) + (1 | species) + (1 | gr(phylogeny, cov = phylo_matrix_concerns)) + (1 | experiment_ID) + (1 | obs) + fcor(VCV_lnVR_concerns)
## Data: data_concerns (Number of observations: 321)
## Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
## total post-warmup draws = 8000
##
## Multilevel Hyperparameters:
## ~experiment_ID (Number of levels: 72)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.16 0.08 0.01 0.33 1.00 878 2212
##
## ~obs (Number of levels: 321)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.78 0.05 0.70 0.88 1.00 2869 5068
##
## ~phylogeny (Number of levels: 22)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.12 0.10 0.00 0.36 1.00 3156 4492
##
## ~ref (Number of levels: 33)
## Estimate Est.Error l-95% CI
## sd(trait_typebody_size) 0.10 0.08 0.00
## sd(trait_typefecundity) 0.11 0.09 0.00
## sd(trait_typesurvival) 0.35 0.19 0.03
## cor(trait_typebody_size,trait_typefecundity) 0.03 0.50 -0.87
## cor(trait_typebody_size,trait_typesurvival) -0.04 0.50 -0.89
## cor(trait_typefecundity,trait_typesurvival) -0.02 0.49 -0.87
## u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(trait_typebody_size) 0.31 1.00 2375 4175
## sd(trait_typefecundity) 0.33 1.00 2540 4109
## sd(trait_typesurvival) 0.77 1.00 1561 2855
## cor(trait_typebody_size,trait_typefecundity) 0.89 1.00 5445 5433
## cor(trait_typebody_size,trait_typesurvival) 0.87 1.00 2578 4513
## cor(trait_typefecundity,trait_typesurvival) 0.87 1.00 3091 5346
##
## ~species (Number of levels: 22)
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sd(Intercept) 0.08 0.06 0.00 0.24 1.00 3266 4264
##
## Regression Coefficients:
## Estimate Est.Error l-95% CI
## trait_typebody_size:warm_coldcold 0.08 0.22 -0.35
## trait_typefecundity:warm_coldcold -0.65 0.37 -1.38
## trait_typesurvival:warm_coldcold 0.12 0.60 -1.06
## trait_typebody_size:warm_coldwarm -0.01 0.16 -0.32
## trait_typefecundity:warm_coldwarm -0.12 0.15 -0.40
## trait_typesurvival:warm_coldwarm 0.22 0.20 -0.18
## trait_typebody_size:warm_coldcold:assay_temp_diff -0.05 0.07 -0.18
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.23 0.07 -0.37
## trait_typesurvival:warm_coldcold:assay_temp_diff -0.01 0.15 -0.30
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.02 0.03 -0.04
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.02 0.02 -0.02
## trait_typesurvival:warm_coldwarm:assay_temp_diff -0.03 0.03 -0.09
## u-95% CI Rhat Bulk_ESS
## trait_typebody_size:warm_coldcold 0.52 1.00 3479
## trait_typefecundity:warm_coldcold 0.07 1.00 5110
## trait_typesurvival:warm_coldcold 1.29 1.00 6476
## trait_typebody_size:warm_coldwarm 0.32 1.00 3018
## trait_typefecundity:warm_coldwarm 0.17 1.00 3178
## trait_typesurvival:warm_coldwarm 0.63 1.00 4401
## trait_typebody_size:warm_coldcold:assay_temp_diff 0.08 1.00 4979
## trait_typefecundity:warm_coldcold:assay_temp_diff -0.08 1.00 5770
## trait_typesurvival:warm_coldcold:assay_temp_diff 0.27 1.00 9420
## trait_typebody_size:warm_coldwarm:assay_temp_diff 0.08 1.00 3242
## trait_typefecundity:warm_coldwarm:assay_temp_diff 0.06 1.00 3196
## trait_typesurvival:warm_coldwarm:assay_temp_diff 0.03 1.00 4146
## Tail_ESS
## trait_typebody_size:warm_coldcold 4800
## trait_typefecundity:warm_coldcold 5892
## trait_typesurvival:warm_coldcold 5805
## trait_typebody_size:warm_coldwarm 4076
## trait_typefecundity:warm_coldwarm 4249
## trait_typesurvival:warm_coldwarm 4377
## trait_typebody_size:warm_coldcold:assay_temp_diff 5687
## trait_typefecundity:warm_coldcold:assay_temp_diff 5867
## trait_typesurvival:warm_coldcold:assay_temp_diff 6624
## trait_typebody_size:warm_coldwarm:assay_temp_diff 4646
## trait_typefecundity:warm_coldwarm:assay_temp_diff 4575
## trait_typesurvival:warm_coldwarm:assay_temp_diff 5704
##
## Further Distributional Parameters:
## Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma 1.00 0.00 1.00 1.00 NA NA NA
##
## Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
# Cleaned model output
as.data.frame(fixef(lnVR_model_concerns, summary = TRUE)) %>%
# Rename categorical variables for cleaner display
rownames_to_column(var = "Parameter") %>%
mutate(Parameter = gsub("trait_type([a-z_]+):warm_cold(cold|warm)", "\\1(\\2)",
Parameter), Parameter = gsub("trait_type([a-z_]+)\\(warm\\):assay_temp_diff",
"\\1(warm): assay_temp_diff", Parameter)) %>%
# Round numbers to 3 decimal points
mutate(across(c(Estimate, Q2.5, Q97.5), ~round(., 3))) %>%
select(Parameter, Estimate, `Lower CI` = Q2.5, `Upper CI` = Q97.5) %>%
kable() %>%
kable_styling(full_width = FALSE, position = "center", fixed_thead = TRUE) %>%
column_spec(1, bold = TRUE, border_right = TRUE) %>%
row_spec(0, background = "white", color = "black", bold = TRUE) %>%
kable_styling(fixed_thead = TRUE)
| Parameter | Estimate | Lower CI | Upper CI |
|---|---|---|---|
| body_size(cold) | 0.081 | -0.346 | 0.518 |
| fecundity(cold) | -0.649 | -1.378 | 0.069 |
| survival(cold) | 0.118 | -1.058 | 1.292 |
| body_size(warm) | -0.007 | -0.321 | 0.320 |
| fecundity(warm) | -0.116 | -0.401 | 0.171 |
| survival(warm) | 0.215 | -0.176 | 0.625 |
| body_size(cold):assay_temp_diff | -0.047 | -0.179 | 0.084 |
| fecundity(cold):assay_temp_diff | -0.227 | -0.370 | -0.081 |
| survival(cold):assay_temp_diff | -0.009 | -0.300 | 0.273 |
| body_size(warm):assay_temp_diff | 0.018 | -0.040 | 0.075 |
| fecundity(warm):assay_temp_diff | 0.020 | -0.019 | 0.059 |
| survival(warm):assay_temp_diff | -0.028 | -0.090 | 0.034 |
Data visualisation
# Discard observations flagged because the number of generations of selection was unclear, as this would only affect the results of meta-regressions
data <- mutate(data, minor_concerns2 = ifelse(minor_concerns == "gen_selection approximate" |
minor_concerns == "gen_selection underestimated",
NA, minor_concerns))
# Filter to data without sources of procedural concerns.
data_concerns <- filter(data, is.na(major_concerns)==T & is.na(minor_concerns2)==T)
# Calculate sample sizes and study counts
sample_sizes_concerns <- data_concerns %>%
group_by(trait_type, warm_cold) %>%
summarise(
estimates = n(),
studies = n_distinct(ref)
)
# Generate predictions
emmeans_concerns <- as.data.frame(emmeans(
lnVR_model_concerns,
specs = ~ assay_temp_diff | trait_type * warm_cold,
at = list(assay_temp_diff = seq(min(data_concerns$assay_temp_diff), max(data_concerns$assay_temp_diff), by = 0.5))))
# Calculate range of values for each category
range_df_concerns <- data_concerns %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_assay_temp_diff = min(assay_temp_diff),
max_assay_temp_diff = max(assay_temp_diff)
)
emmeans_concerns$trait_type <- factor(emmeans_concerns$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_concerns$warm_cold <- factor(emmeans_concerns$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_concerns <- emmeans_concerns %>%
left_join(range_df_concerns, by = c("trait_type", "warm_cold")) %>%
filter(
assay_temp_diff >= min_assay_temp_diff,
assay_temp_diff <= max_assay_temp_diff
)
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_vline(xintercept = 0, # Vertical line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data_concerns, # Effect sizes, scaled by precision
aes(x = assay_temp_diff,
y = lnVR,
size = 1/sqrt(var_lnVR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_concerns, # Shaded area for credible intervals
aes(x = assay_temp_diff,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_concerns, # Predicted regression line
aes(y = emmean,
x = assay_temp_diff,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes_concerns,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -2, -2.75), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Assay temperature difference", y = "lnVR",
col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 25, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 22)) +
guides(fill = "none", size = "none")+
coord_cartesian(ylim = c(-3.5, 3.5))
ggsave(file = "fig/lnVR_concerns.png", width = 12, height = 7, dpi = 500)
Figures for publication
Note that Figure 1 was created without code.
Figure 2
# Match species to the OTL database
resolved_names <- tnrs_match_names(unique(data$species), context_name = "Animals")
# Create tree
tree <- tol_induced_subtree(ott_ids = resolved_names$ott_id)
tree$tip.label <- strip_ott_ids(tree$tip.label) # Remove ott IDs for presentation
tree <- multi2di(tree, random = TRUE) # Resolve polytomy at random, but it matches classification from Cornetti et al. 2019. Molecular Phylogenetics and Evolution; with D. pulex and D. pulicaria being more closely related to eachother than D. magna
phylo_tree <- compute.brlen(tree, method = "Grafen", power = 1) # Compute branch lengths
# Remove underscore for species names
phylo_tree$tip.label <- gsub("_", " ", phylo_tree$tip.label) # Add underscore between species names
# Create a data summary for each species
species_summary <- data %>%
group_by(tip.label = species, warm_cold) %>%
summarise(n_effect_sizes = n(), .groups = "drop")
species_totals <- species_summary %>%
group_by(tip.label) %>%
summarise(total_effect_sizes = sum(n_effect_sizes), .groups = "drop")
# Order species
species_order <- rev(c(
"Drosophila bipectinata",
"Drosophila pseudoananassae",
"Drosophila melanogaster",
"Drosophila serrata",
"Drosophila subobscura",
"Drosophila hydei",
"Drosophila buzzatii",
"Musca domestica",
"Sepsis punctum",
"Callosobruchus chinensis",
"Callosobruchus maculatus",
"Zabrotes subfasciatus",
"Tribolium castaneum",
"Daphnia pulicaria",
"Daphnia pulex",
"Daphnia magna",
"Simocephalus vetulus",
"Apocyclops royi",
"Eurytemora affinis",
"Rhizoglyphus robini",
"Caenorhabditis remanei",
"Ophryotrocha labronica",
"Brachionus plicatilis",
"Poecilia reticulata",
"Danio rerio"
))
species_summary <- species_summary %>%
mutate(tip.label = factor(tip.label, levels = species_order))
# Plot tree
tree_plot <- ggtree(phylo_tree, lwd = 1.25) +
geom_tiplab(size = 5,
offset = 0.075,
fontface = "italic") +
xlim(0, 2.5) +
theme(plot.margin = unit(c(0, 1.5, 0, 0), "cm"))
barplot <- ggplot(species_summary, aes(x = tip.label,
y = n_effect_sizes,
fill = warm_cold)) +
geom_bar(stat = "identity",
position = position_dodge2(),
width = 0.8) +
geom_hline(yintercept = 0,
color = "black",
size = 0.75) + # Add vertical line
geom_vline(xintercept = 0.4,
color = "black",
size = 0.5) + # Add horizontal line
geom_text(data = species_totals,
aes(x = tip.label,
y = -8,
label = total_effect_sizes),
inherit.aes = FALSE,
size = 5) + # Add total count annotation
scale_fill_manual(values = c("Cold" = "#06B4BA", "Warm" = "#E80756")) +
coord_flip() +
scale_y_continuous(expand = expansion(mult = c(0.05, 0.2))) + # Add space on the right
theme_minimal() +
labs(x = NULL, y = "Number of effect sizes") +
theme(axis.text.y = element_blank(), # Remove y-axis text for alignment
axis.ticks.y = element_blank(),
panel.grid = element_blank(), # Remove grid
plot.background = element_rect(fill = "white", color = NA), # White background
legend.position = "none",
axis.text = element_text (size = 14), col = "black",
axis.title = element_text(size = 18))
tree_plot | barplot
ggsave(file = "fig/figure_2.png", width = 12, height = 10, dpi = 500)
Figure 3
# Load model
lnRR_model_assay_temp_diff <- readRDS("RData/lnRR_model_assay_temp_diff.rds")
# Generate predictions
emmeans_assay_temp_diff <- as.data.frame(emmeans(
lnRR_model_assay_temp_diff,
specs = ~ assay_temp_diff | trait_type * warm_cold,
at = list(assay_temp_diff = seq(min(data$assay_temp_diff), max(data$assay_temp_diff), by = 0.5)))
)
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_assay_temp_diff = min(assay_temp_diff),
max_assay_temp_diff = max(assay_temp_diff)
)
emmeans_assay_temp_diff$trait_type <- factor(emmeans_assay_temp_diff$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_assay_temp_diff$warm_cold <- factor(emmeans_assay_temp_diff$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_assay_temp_diff <- emmeans_assay_temp_diff %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
assay_temp_diff >= min_assay_temp_diff,
assay_temp_diff <= max_assay_temp_diff
)
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_vline(xintercept = 0, # Vertical line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = assay_temp_diff,
y = lnRR,
size = 1/sqrt(var_lnRR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_assay_temp_diff, # Shaded area for credible intervals
aes(x = assay_temp_diff,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_assay_temp_diff, # Predicted regression line
aes(y = emmean,
x = assay_temp_diff,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -0.9, -1.3), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Assay temperature difference", y = "lnRR", col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(4, 10))+
theme(text = element_text(size = 25, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 22)) +
guides(fill = "none", size = "none")+
coord_cartesian(ylim = c(-1.5, 1.5))
ggsave(file = "fig/figure_3.png", width = 12, height = 10, dpi = 500)
Figure 4
# Load models
lnRR_model_sel_temp_diff <- readRDS("RData/lnRR_model_sel_temp_diff.rds")
lnRR_model_gen_selection <- readRDS("RData/lnRR_model_gen_selection.rds")
lnRR_model_gen_common_garden <- readRDS("RData/lnRR_model_gen_common_garden.rds")
lnRR_model_pop_size <- readRDS("RData/lnRR_model_pop_size.rds")
lnRR_model_all_moderators <- readRDS("RData/lnRR_model_all_moderators.rds")
##################### Selection temperature ################################
# Generate predictions
emmeans_sel_temp_diff <- as.data.frame(emmeans(
lnRR_model_sel_temp_diff,
specs = ~ select_temp_diff | trait_type * warm_cold,
at = list(select_temp_diff = seq(min(data$select_temp_diff), max(data$select_temp_diff), by = 0.1),
assay_temp_diff = 0))
) # Conditional effects on assay_temp_diff = 0
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_select_temp_diff = min(select_temp_diff),
max_select_temp_diff = max(select_temp_diff)
)
emmeans_sel_temp_diff$trait_type <- factor(emmeans_sel_temp_diff$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_sel_temp_diff$warm_cold <- factor(emmeans_sel_temp_diff$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_sel_temp_diff <- emmeans_sel_temp_diff %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
select_temp_diff >= min_select_temp_diff,
select_temp_diff <= max_select_temp_diff
)
# Plot
sel_temp_diff <-
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = select_temp_diff,
y = lnRR,
size = 1/sqrt(var_lnRR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_sel_temp_diff, # Shaded area for credible intervals
aes(x = select_temp_diff,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_sel_temp_diff, # Predicted regression line
aes(y = emmean,
x = select_temp_diff,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -0.9, -1.3), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Selection temperature difference", y = "lnRR", col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(4, 10))+
theme(text = element_text(size = 30, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 28),
plot.margin = unit(c(1, 0, 0, 0), "cm")) +
guides(fill = "none", size = "none", color = "none")+
coord_cartesian(ylim = c(-1.5, 1.5))
############## Generations of selection ##################################
# Generate predictions
emmeans_gen_selection <- as.data.frame(emmeans(
lnRR_model_gen_selection,
specs = ~ gen_selection | trait_type * warm_cold,
at = list(gen_selection = seq(min(data$gen_selection), max(data$gen_selection), by = 1),
assay_temp_diff = 0))
) # Conditional effects on assay_temp_diff = 0
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_gen_selection = min(gen_selection),
max_gen_selection = max(gen_selection)
)
emmeans_gen_selection$trait_type <- factor(emmeans_gen_selection$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_gen_selection$warm_cold <- factor(emmeans_gen_selection$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_gen_selection <- emmeans_gen_selection %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
gen_selection >= min_gen_selection,
gen_selection <= max_gen_selection
)
# Plot
gen_selection <-
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = gen_selection,
y = lnRR,
size = 1/sqrt(var_lnRR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_gen_selection, # Shaded area for credible intervals
aes(x = gen_selection,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_gen_selection, # Predicted regression line
aes(y = emmean,
x = gen_selection,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -0.9, -1.3), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Number of generations of selection", y = "lnRR", col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(4, 10))+
theme(text = element_text(size = 30, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 28),
plot.margin = unit(c(1.5, 0, 0, 0), "cm")) +
guides(fill = "none", size = "none", color = "none")+
coord_cartesian(ylim = c(-1.5, 1.5),
xlim = c(0, 150))
################### Generations of common garden #################################
# Generate predictions
emmeans_gen_common_garden <- as.data.frame(emmeans(
lnRR_model_gen_common_garden,
specs = ~ gen_common_garden | trait_type * warm_cold,
at = list(gen_common_garden = seq(min(data$gen_common_garden), max(data$gen_common_garden), by = 1),
assay_temp_diff = 0))
) # Conditional effects on assay_temp_diff = 0
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_gen_common_garden = min(gen_common_garden),
max_gen_common_garden = max(gen_common_garden)
)
emmeans_gen_common_garden$trait_type <- factor(emmeans_gen_common_garden$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_gen_common_garden$warm_cold <- factor(emmeans_gen_common_garden$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_gen_common_garden <- emmeans_gen_common_garden %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
gen_common_garden >= min_gen_common_garden,
gen_common_garden <= max_gen_common_garden
)
# Plot
gen_common_garden <-
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = gen_common_garden,
y = lnRR,
size = 1/sqrt(var_lnRR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_gen_common_garden, # Shaded area for credible intervals
aes(x = gen_common_garden,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_gen_common_garden, # Predicted regression line
aes(y = emmean,
x = gen_common_garden,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -0.9, -1.3), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Number of generations of common garden", y = "", col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(4, 10))+
theme(text = element_text(size = 30, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 28),
plot.margin = unit(c(1.5, 1, 0, 0.5), "cm")) +
guides(fill = "none", size = "none", color = "none")+
coord_cartesian(ylim = c(-1.5, 1.5),
xlim = c(0, 11.5))
##################### Initial population size ##################################
# Generate predictions
emmeans_pop_size <- as.data.frame(emmeans(
lnRR_model_pop_size,
specs = ~ pop_size | trait_type * warm_cold,
at = list(pop_size = seq(min(data$pop_size, na.rm=T), max(data$pop_size, na.rm=T), by = 50),
assay_temp_diff = 0))
) # Conditional effects on assay_temp_diff = 0
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_pop_size = min(pop_size, na.rm=T),
max_pop_size = max(pop_size, na.rm=T)
)
emmeans_pop_size$trait_type <- factor(emmeans_pop_size$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_pop_size$warm_cold <- factor(emmeans_pop_size$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_pop_size <- emmeans_pop_size %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
pop_size >= min_pop_size,
pop_size <= max_pop_size
)
# Calculate sample sizes and study counts for population size
sample_sizes_pop_size <- data %>%
filter(is.na(pop_size)==FALSE) %>%
group_by(trait_type, warm_cold) %>%
summarise(
estimates = n(),
studies = n_distinct(ref)
)
# Plot
pop_size <-
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = pop_size,
y = lnRR,
size = 1/sqrt(var_lnRR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_pop_size, # Shaded area for credible intervals
aes(x = pop_size,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_pop_size, # Predicted regression line
aes(y = emmean,
x = pop_size,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes_pop_size,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -0.9, -1.3), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Population size", y = "", col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(4, 10))+
theme(text = element_text(size = 30, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 28),
plot.margin = unit(c(0, 1, 0, 0.5), "cm")) +
guides(fill = "none", size = "none", color = "none")+
coord_cartesian(ylim = c(-1.5, 1.5))
##################### Full model ##################################
# Generate predictions at the mean of each moderator
emmeans_full_model <- as.data.frame(emmeans(
lnRR_model_all_moderators,
specs = ~ assay_temp_diff * select_temp_diff * gen_selection * gen_common_garden | trait_type * warm_cold,
at = list(assay_temp_diff = seq(min(data$assay_temp_diff), max(data$assay_temp_diff), by = 0.5))))
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_assay_temp_diff = min(assay_temp_diff),
max_assay_temp_diff = max(assay_temp_diff)
)
emmeans_full_model$trait_type <- factor(emmeans_full_model$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_full_model$warm_cold <- factor(emmeans_full_model$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_full_model <- emmeans_full_model%>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
assay_temp_diff >= min_assay_temp_diff,
assay_temp_diff <= max_assay_temp_diff
)
# Plot
assay_temp <-
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_vline(xintercept = 0, # Vertical line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = assay_temp_diff,
y = lnRR,
size = 1/sqrt(var_lnRR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_full_model, # Shaded area for credible intervals
aes(x = assay_temp_diff,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_full_model, # Predicted regression line
aes(y = emmean,
x = assay_temp_diff,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -2, -2.75), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Assay temperature difference", y = "lnRR",
col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(4, 10))+
theme(text = element_text(size = 30, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 28)) +
guides(fill = "none", size = "none", col = "none")+
coord_cartesian(ylim = c(-1.5, 1.5))
assay_temp / (sel_temp_diff | pop_size) / (gen_selection | gen_common_garden)
ggsave(file = "fig/figure_4.png", width = 20, height = 32, dpi = 500)
Figure 5
# Load model
lnVR_model_assay_temp_diff <- readRDS("RData/lnVR_model_assay_temp_diff.rds")
# Generate predictions
emmeans_assay_temp_diff <- as.data.frame(emmeans(
lnVR_model_assay_temp_diff,
specs = ~ assay_temp_diff | trait_type * warm_cold,
at = list(assay_temp_diff = seq(min(data$assay_temp_diff), max(data$assay_temp_diff), by = 0.5)))
)
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_assay_temp_diff = min(assay_temp_diff),
max_assay_temp_diff = max(assay_temp_diff)
)
emmeans_assay_temp_diff$trait_type <- factor(emmeans_assay_temp_diff$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_assay_temp_diff$warm_cold <- factor(emmeans_assay_temp_diff$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_assay_temp_diff <- emmeans_assay_temp_diff %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
assay_temp_diff >= min_assay_temp_diff,
assay_temp_diff <= max_assay_temp_diff
)
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_vline(xintercept = 0, # Vertical line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = assay_temp_diff,
y = lnVR,
size = 1/sqrt(var_lnVR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_assay_temp_diff, # Shaded area for credible intervals
aes(x = assay_temp_diff,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_assay_temp_diff, # Predicted regression line
aes(y = emmean,
x = assay_temp_diff,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -2, -2.75), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Assay temperature difference", y = "lnVR",
col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 25, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 22)) +
guides(fill = "none", size = "none")+
coord_cartesian(ylim = c(-3.5, 3.5))
ggsave(file = "fig/figure_5.png", width = 12, height = 10, dpi = 500)
Figure 6
# Load models
lnVR_model_sel_temp_diff <- readRDS("RData/lnVR_model_sel_temp_diff.rds")
lnVR_model_gen_selection <- readRDS("RData/lnVR_model_gen_selection.rds")
lnVR_model_gen_common_garden <- readRDS("RData/lnVR_model_gen_common_garden.rds")
lnVR_model_pop_size <- readRDS("RData/lnVR_model_pop_size.rds")
lnVR_model_all_moderators <- readRDS("RData/lnVR_model_all_moderators.rds")
##################### Selection temperature ################################
# Generate predictions
emmeans_sel_temp_diff <- as.data.frame(emmeans(
lnVR_model_sel_temp_diff,
specs = ~ select_temp_diff | trait_type * warm_cold,
at = list(select_temp_diff = seq(min(data$select_temp_diff), max(data$select_temp_diff), by = 0.1),
assay_temp_diff = 0))
) # Conditional effects on assay_temp_diff = 0
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_select_temp_diff = min(select_temp_diff),
max_select_temp_diff = max(select_temp_diff)
)
emmeans_sel_temp_diff$trait_type <- factor(emmeans_sel_temp_diff$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_sel_temp_diff$warm_cold <- factor(emmeans_sel_temp_diff$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_sel_temp_diff <- emmeans_sel_temp_diff %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
select_temp_diff >= min_select_temp_diff,
select_temp_diff <= max_select_temp_diff
)
# Plot
sel_temp_diff <-
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = select_temp_diff,
y = lnVR,
size = 1/sqrt(var_lnVR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_sel_temp_diff, # Shaded area for credible intervals
aes(x = select_temp_diff,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_sel_temp_diff, # Predicted regression line
aes(y = emmean,
x = select_temp_diff,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -2, -2.75), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Selection temperature difference", y = "lnVR", col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 30, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 28),
plot.margin = unit(c(1, 0, 0, 0), "cm")) +
guides(fill = "none", size = "none", color = "none")+
coord_cartesian(ylim = c(-3.5, 3.5))
############## Generations of selection ##################################
# Generate predictions
emmeans_gen_selection <- as.data.frame(emmeans(
lnVR_model_gen_selection,
specs = ~ gen_selection | trait_type * warm_cold,
at = list(gen_selection = seq(min(data$gen_selection), max(data$gen_selection), by = 1),
assay_temp_diff = 0))
) # Conditional effects on assay_temp_diff = 0
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_gen_selection = min(gen_selection),
max_gen_selection = max(gen_selection)
)
emmeans_gen_selection$trait_type <- factor(emmeans_gen_selection$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_gen_selection$warm_cold <- factor(emmeans_gen_selection$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_gen_selection <- emmeans_gen_selection %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
gen_selection >= min_gen_selection,
gen_selection <= max_gen_selection
)
# Plot
gen_selection <-
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = gen_selection,
y = lnVR,
size = 1/sqrt(var_lnVR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_gen_selection, # Shaded area for credible intervals
aes(x = gen_selection,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_gen_selection, # Predicted regression line
aes(y = emmean,
x = gen_selection,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -2, -2.75), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Number of generations of selection", y = "lnRR", col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 30, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 28),
plot.margin = unit(c(1.5, 0, 0, 0), "cm")) +
guides(fill = "none", size = "none", color = "none")+
coord_cartesian(ylim = c(-3.5, 3.5),
xlim = c(0, 150))
################### Generations of common garden #################################
# Generate predictions
emmeans_gen_common_garden <- as.data.frame(emmeans(
lnVR_model_gen_common_garden,
specs = ~ gen_common_garden | trait_type * warm_cold,
at = list(gen_common_garden = seq(min(data$gen_common_garden), max(data$gen_common_garden), by = 1),
assay_temp_diff = 0))
) # Conditional effects on assay_temp_diff = 0
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_gen_common_garden = min(gen_common_garden),
max_gen_common_garden = max(gen_common_garden)
)
emmeans_gen_common_garden$trait_type <- factor(emmeans_gen_common_garden$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_gen_common_garden$warm_cold <- factor(emmeans_gen_common_garden$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_gen_common_garden <- emmeans_gen_common_garden %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
gen_common_garden >= min_gen_common_garden,
gen_common_garden <= max_gen_common_garden
)
# Plot
gen_common_garden <-
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = gen_common_garden,
y = lnVR,
size = 1/sqrt(var_lnVR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_gen_common_garden, # Shaded area for credible intervals
aes(x = gen_common_garden,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_gen_common_garden, # Predicted regression line
aes(y = emmean,
x = gen_common_garden,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -2, -2.75), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Number of generations of common garden", y = "", col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 30, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 28),
plot.margin = unit(c(1.5, 1, 0, 0.5), "cm")) +
guides(fill = "none", size = "none", color = "none")+
coord_cartesian(ylim = c(-3.5, 3.5),
xlim = c(0, 11.5))
##################### Initial population size ##################################
# Generate predictions
emmeans_pop_size <- as.data.frame(emmeans(
lnVR_model_pop_size,
specs = ~ pop_size | trait_type * warm_cold,
at = list(pop_size = seq(min(data$pop_size, na.rm=T), max(data$pop_size, na.rm=T), by = 50),
assay_temp_diff = 0))
) # Conditional effects on assay_temp_diff = 0
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_pop_size = min(pop_size, na.rm=T),
max_pop_size = max(pop_size, na.rm=T)
)
emmeans_pop_size$trait_type <- factor(emmeans_pop_size$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_pop_size$warm_cold <- factor(emmeans_pop_size$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_pop_size <- emmeans_pop_size %>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
pop_size >= min_pop_size,
pop_size <= max_pop_size
)
# Calculate sample sizes and study counts for population size
sample_sizes_pop_size <- data %>%
filter(is.na(pop_size)==FALSE) %>%
group_by(trait_type, warm_cold) %>%
summarise(
estimates = n(),
studies = n_distinct(ref)
)
# Plot
pop_size <-
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = pop_size,
y = lnVR,
size = 1/sqrt(var_lnVR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_pop_size, # Shaded area for credible intervals
aes(x = pop_size,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_pop_size, # Predicted regression line
aes(y = emmean,
x = pop_size,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes_pop_size,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -2, -2.75), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Population size", y = "", col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 30, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 28),
plot.margin = unit(c(0, 1, 0, 0.5), "cm")) +
guides(fill = "none", size = "none", color = "none")+
coord_cartesian(ylim = c(-3.5, 3.5))
##################### Full model ##################################
# Generate predictions at the mean of each moderator
emmeans_full_model <- as.data.frame(emmeans(
lnVR_model_all_moderators,
specs = ~ assay_temp_diff * select_temp_diff * gen_selection * gen_common_garden | trait_type * warm_cold,
at = list(assay_temp_diff = seq(min(data$assay_temp_diff), max(data$assay_temp_diff), by = 0.5))))
# Calculate range of values for each category
range_df <- data %>%
group_by(trait_type, warm_cold) %>%
summarize(
min_assay_temp_diff = min(assay_temp_diff),
max_assay_temp_diff = max(assay_temp_diff)
)
emmeans_full_model$trait_type <- factor(emmeans_full_model$trait_type,
levels = c("body_size", "fecundity", "survival"),
labels = c("Body size", "Fecundity", "Survival"))
emmeans_full_model$warm_cold <- factor(emmeans_full_model$warm_cold,
levels = c("cold", "warm"),
labels = c("Cold", "Warm"))
# Tailor predictions to the range of the data
emmeans_full_model <- emmeans_full_model%>%
left_join(range_df, by = c("trait_type", "warm_cold")) %>%
filter(
assay_temp_diff >= min_assay_temp_diff,
assay_temp_diff <= max_assay_temp_diff
)
# Plot
ggplot() +
geom_hline(yintercept = 0, # Horizontal line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_vline(xintercept = 0, # Vertical line
linetype = 2,
colour = "black",
alpha = 0.75) +
geom_point(data = data, # Effect sizes, scaled by precision
aes(x = assay_temp_diff,
y = lnVR,
size = 1/sqrt(var_lnVR),
fill = warm_cold),
shape = 21,
alpha = 0.7,
stroke = 1,
position = position_jitter(width = 0.05)) +
geom_ribbon(data = emmeans_full_model, # Shaded area for credible intervals
aes(x = assay_temp_diff,
ymin = lower.HPD,
ymax = upper.HPD,
fill = warm_cold,
group = warm_cold),
alpha = .5) +
geom_line(data = emmeans_full_model, # Predicted regression line
aes(y = emmean,
x = assay_temp_diff,
color = warm_cold,
group = warm_cold),
size = 1.5) +
geom_text(data = sample_sizes,
aes(x = Inf,
y = ifelse(warm_cold == "Warm", -2, -2.75), # Sample sizes
label = paste0("k = ", estimates, " (", studies, ")"),
color = warm_cold),
hjust = 1.05, size = 5, show.legend = FALSE) +
facet_wrap(~ trait_type, ncol = 1) + # Different panels for each trait
scale_fill_manual(values = c("#06B4BA", "#E80756"))+
scale_color_manual(values = c("#06B4BA", "#E80756"))+
labs(x = "Assay temperature difference", y = "lnVR",
col = "Regime") +
theme_bw() +
scale_size_continuous(range = c(2, 8))+
theme(text = element_text(size = 30, color = "black"),
legend.title = ggplot2::element_text(size = 18),
legend.text = ggplot2::element_text(size = 16),
axis.text.y = ggplot2::element_text(size = 20,
colour = "black", hjust = 0.5),
axis.text.x = ggplot2::element_text(size = 20),
panel.border = element_rect(color = "black", size = 1.5),
strip.background = element_blank(),
strip.text = element_text(color = "black", size = 28)) +
guides(fill = "none", size = "none")+
coord_cartesian(ylim = c(-3.5, 3.5))
assay_temp / (sel_temp_diff | pop_size) / (gen_selection | gen_common_garden)
ggsave(file = "fig/figure_6.png", width = 20, height = 32, dpi = 500)
Figure S1
Package information
sessionInfo()
## R version 4.2.0 (2022-04-22 ucrt)
## Platform: x86_64-w64-mingw32/x64 (64-bit)
## Running under: Windows 10 x64 (build 22631)
##
## Matrix products: default
##
## locale:
## [1] LC_COLLATE=English_Australia.utf8 LC_CTYPE=English_Australia.utf8
## [3] LC_MONETARY=English_Australia.utf8 LC_NUMERIC=C
## [5] LC_TIME=English_Australia.utf8
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] ggtreeExtra_1.7.0 ggtree_3.5.0.901 kableExtra_1.3.4
## [4] ggbeeswarm_0.7.2 tidybayes_3.0.6 emmeans_1.7.3
## [7] metafor_4.2-0 numDeriv_2016.8-1.1 metadat_1.2-0
## [10] Matrix_1.5-4 orchaRd_2.0 brms_2.22.0
## [13] Rcpp_1.0.10 rotl_3.0.14 ape_5.7-1
## [16] lubridate_1.9.2 forcats_1.0.0 stringr_1.5.0
## [19] dplyr_1.1.2 purrr_1.0.1 readr_2.1.4
## [22] tidyr_1.3.0 tibble_3.2.1 ggplot2_3.5.1
## [25] tidyverse_2.0.0
##
## loaded via a namespace (and not attached):
## [1] backports_1.4.1 systemfonts_1.0.4 plyr_1.8.8
## [4] lazyeval_0.2.2 splines_4.2.0 svUnit_1.0.6
## [7] rncl_0.8.7 TH.data_1.1-2 rstantools_2.3.1
## [10] inline_0.3.19 digest_0.6.31 yulab.utils_0.0.6
## [13] htmltools_0.5.5 fansi_1.0.4 magrittr_2.0.3
## [16] checkmate_2.2.0 tzdb_0.4.0 RcppParallel_5.1.7
## [19] matrixStats_0.63.0 sandwich_3.1-1 svglite_2.1.1
## [22] timechange_0.2.0 rmdformats_1.0.4 prettyunits_1.1.1
## [25] colorspace_2.1-0 rvest_1.0.3 ggdist_3.2.1
## [28] textshaping_0.3.6 xfun_0.39 callr_3.7.3
## [31] crayon_1.5.2 jsonlite_1.8.7 survival_3.7-0
## [34] zoo_1.8-12 glue_1.6.2 gtable_0.3.4
## [37] webshot_0.5.4 V8_6.0.0 distributional_0.3.2
## [40] pkgbuild_1.4.2 rstan_2.32.6 rentrez_1.2.3
## [43] abind_1.4-5 scales_1.3.0 mvtnorm_1.1-3
## [46] viridisLite_0.4.2 xtable_1.8-4 progress_1.2.2
## [49] gridGraphics_0.5-1 tidytree_0.4.2 stats4_4.2.0
## [52] StanHeaders_2.32.10 httr_1.4.7 arrayhelpers_1.1-0
## [55] posterior_1.6.0 pkgconfig_2.0.3 loo_2.8.0
## [58] XML_3.99-0.14 farver_2.1.1 sass_0.4.9
## [61] utf8_1.2.3 ggplotify_0.1.0 tidyselect_1.2.1
## [64] labeling_0.4.3 rlang_1.1.1 reshape2_1.4.4
## [67] munsell_0.5.0 tools_4.2.0 cachem_1.0.8
## [70] cli_3.6.1 generics_0.1.3 mathjaxr_1.6-0
## [73] evaluate_0.21 fastmap_1.1.1 yaml_2.3.7
## [76] ragg_1.2.5 processx_3.8.1 knitr_1.44
## [79] nlme_3.1-157 formatR_1.14 aplot_0.1.10
## [82] xml2_1.3.6 compiler_4.2.0 bayesplot_1.10.0
## [85] rstudioapi_0.15.0 beeswarm_0.4.0 curl_5.0.0
## [88] treeio_1.21.0 bslib_0.5.1 stringi_1.7.12
## [91] highr_0.10 ps_1.7.5 Brobdingnag_1.2-9
## [94] lattice_0.20-45 tensorA_0.36.2 vctrs_0.6.2
## [97] pillar_1.9.0 lifecycle_1.0.3 jquerylib_0.1.4
## [100] bridgesampling_1.1-2 estimability_1.3 patchwork_1.2.0.9000
## [103] QuickJSR_1.3.1 R6_2.5.1 bookdown_0.34
## [106] gridExtra_2.3 vipor_0.4.5 codetools_0.2-18
## [109] MASS_7.3-56 withr_2.5.0 multcomp_1.4-26
## [112] parallel_4.2.0 hms_1.1.3 grid_4.2.0
## [115] ggfun_0.0.9 coda_0.19-4 rmarkdown_2.25.1
## [118] ggnewscale_0.4.10.9000